diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f4515d..3a70fa5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,96 +1,113 @@ # Spectacle project project(Spectacle) # KDE Application Version, managed by release script -set(KDE_APPLICATIONS_VERSION_MAJOR "15") -set(KDE_APPLICATIONS_VERSION_MINOR "12") -set(KDE_APPLICATIONS_VERSION_MICRO "1") +set(KDE_APPLICATIONS_VERSION_MAJOR "16") +set(KDE_APPLICATIONS_VERSION_MINOR "07") +set(KDE_APPLICATIONS_VERSION_MICRO "70") set(KDE_APPLICATIONS_VERSION "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}.${KDE_APPLICATIONS_VERSION_MICRO}") set(SPECTACLE_VERSION ${KDE_APPLICATIONS_VERSION}) # minimum requirements cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) set(QT_MIN_VERSION "5.4.0") set(KF5_MIN_VERSION "5.18.0") set(PLASMA_MIN_VERSION "5.4.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set( CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ) # set up kf5 include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMSetupVersion) include(FeatureSummary) find_package( Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core Widgets DBus PrintSupport Quick ) find_package( KF5 ${KF5_MIN_VERSION} REQUIRED CoreAddons WidgetsAddons DBusAddons Notifications Config I18n KIO XmlGui WindowSystem DocTools Declarative ) # optional components find_package(KF5Kipi) if (KF5Kipi_FOUND) set(KIPI_FOUND 1) endif () find_package(KDEExperimentalPurpose) if (KDEExperimentalPurpose_FOUND) set(PURPOSE_FOUND 1) endif() find_package(XCB COMPONENTS XFIXES IMAGE UTIL CURSOR) +set(XCB_COMPONENTS_ERRORS FALSE) if (XCB_FOUND) - find_package(Qt5X11Extras ${QT_MIN_VERSION} REQUIRED) - find_package(KF5Screen ${PLASMA_MIN_VERSION} REQUIRED) + find_package(Qt5X11Extras ${QT_MIN_VERSION} REQUIRED) + find_package(KF5Screen ${PLASMA_MIN_VERSION} REQUIRED) +endif() +set(XCB_COMPONENTS_FOUND TRUE) +if(NOT XCB_XFIXES_FOUND) + set(XCB_COMPONENTS_ERRORS "${XCB_COMPONENTS_ERRORS} XCB-XFIXES ") + set(XCB_COMPONENTS_FOUND FALSE) +endif() +if(NOT XCB_IMAGE_FOUND) + set(XCB_COMPONENTS_ERRORS "${XCB_COMPONENTS_ERRORS} XCB-IMAGE ") + set(XCB_COMPONENTS_FOUND FALSE) +endif() +if(NOT XCB_UTIL_FOUND) + set(XCB_COMPONENTS_ERRORS "${XCB_COMPONENTS_ERRORS} XCB-UTIL ") + set(XCB_COMPONENTS_FOUND FALSE) +endif() +if(NOT XCB_CURSOR_FOUND) + set(XCB_COMPONENTS_ERRORS "${XCB_COMPONENTS_ERRORS} XCB-CURSOR ") + set(XCB_COMPONENTS_FOUND FALSE) endif() # fail build if none of the platform backends can be found - -if (!XCB_FOUND) - message(FATAL_ERROR "No suitable backend platform was found. Currenty supported platforms are: XCB") +if (NOT XCB_FOUND OR NOT XCB_COMPONENTS_FOUND) + message(FATAL_ERROR "No suitable backend platform was found. Currenty supported platforms are: XCB Components Required: ${XCB_COMPONENTS_ERRORS}") endif() # hand off to subdirectories add_subdirectory(src) add_subdirectory(dbus) add_subdirectory(desktop) add_subdirectory(icons) add_subdirectory(doc) # summaries feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/ExtraDesktop.sh b/ExtraDesktop.sh new file mode 100644 index 0000000..07a5fd8 --- /dev/null +++ b/ExtraDesktop.sh @@ -0,0 +1,4 @@ +#! /bin/sh +#This file outputs in a separate line each file with a .desktop syntax +#that needs to be translated but has a non .desktop extension +find -name \*.khotkeys -print diff --git a/desktop/org.kde.spectacle.desktop b/desktop/org.kde.spectacle.desktop index fa6a77b..9574963 100644 --- a/desktop/org.kde.spectacle.desktop +++ b/desktop/org.kde.spectacle.desktop @@ -1,168 +1,178 @@ [Desktop Entry] GenericName=Screenshot Capture Utility GenericName[ar]=أداة لأخذ اللقطات GenericName[ast]=Utilidá de captura de pantalla GenericName[bg]=Инструмент за заснемане на екрана GenericName[ca]=Utilitat de captura de pantalla GenericName[ca@valencia]=Utilitat de captura de pantalla GenericName[cs]=Nástroj na snímání obrazovky GenericName[de]=Dienstprogramm für Bildschirmfotos +GenericName[el]=Εργαλείο λήψης στιγμιοτύπων οθόνης GenericName[en_GB]=Screenshot Capture Utility GenericName[es]=Utilidad de capturas de pantalla GenericName[fi]=Kuvankaappausohjelma GenericName[gl]=Utilidade para facer capturas de pantalla GenericName[it]=Accessorio per catturare schermate GenericName[ko]=화면 캡처 유틸리티 GenericName[lt]=KDE ekranvaizdžių paveikslavimas GenericName[nl]=Hulpmiddel voor het vangen van een schermafdruk GenericName[nn]=Verktøy for å lagra skjermbilete GenericName[pl]=Narzędzie do przechwytywania ekranu GenericName[pt]=Utilitário de Captura de Imagens GenericName[pt_BR]=Utilitário de captura de tela GenericName[ro]=Utilitar de capturare a ecranului GenericName[ru]=Создание снимков экрана GenericName[sk]=Nástroj na snímanie obrazovky GenericName[sl]=Pripomoček za zajem zaslona GenericName[sv]=Verktyg för att ta skärmbilder GenericName[uk]=Програма для створення знімків екрана GenericName[x-test]=xxScreenshot Capture Utilityxx GenericName[zh_CN]=屏幕截图工具 Name=Spectacle Name[ast]=Spectacle Name[ca]=Spectacle Name[ca@valencia]=Spectacle Name[da]=Spectacle Name[de]=Spectacle +Name[el]=Spectacle Name[en_GB]=Spectacle Name[es]=Spectacle Name[fi]=Spectacle Name[gl]=Spectacle Name[it]=Spectacle Name[ko]=Spectacle Name[lt]=Spectacle Name[nl]=Spectacle Name[nn]=Spectacle Name[pl]=Spectacle Name[pt]=Spectacle Name[pt_BR]=Spectacle Name[ru]=Spectacle Name[sk]=Spectacle Name[sl]=Spectacle Name[sv]=Spectacle Name[uk]=Spectacle Name[x-test]=xxSpectaclexx Name[zh_CN]=Spectacle Categories=Qt;KDE;Utility; MimeType= Exec=spectacle Icon=spectacle Type=Application Terminal=false StartupNotify=false Actions=FullScreenScreenShot;CurrentMonitorScreenShot;ActiveWindowScreenShot;RectangularRegionScreenShot; X-DBUS-StartupType=Multi X-DBUS-ServiceName=org.kde.Spectacle [Desktop Action FullScreenScreenShot] Name=Capture Entire Desktop Name[ar]=التقط سطح المكتب بالكامل Name[ast]=Capturar tol escritoriu Name[ca]=Captura l'escriptori sencer Name[ca@valencia]=Captura l'escriptori sencer Name[de]=Die gesamte Arbeitsfläche aufnehmen +Name[el]=Λήψη όλης της επιφάνειας εργασίας Name[en_GB]=Capture Entire Desktop Name[es]=Capturar todo el escritorio Name[fi]=Kaappaa koko työpöytä Name[gl]=Capturar todo o escritorio Name[it]=Cattura l'intero desktop Name[ko]=전체 데스크톱 찍기 Name[lt]=Paveiksluoti visą darbalaukį Name[nl]=Gehele bureaublad opnemen Name[nn]=Ta bilete av heile skrivebordet Name[pl]=Przechwyć cały pulpit Name[pt]=Capturar Todo o Ecrã Name[pt_BR]=Capturar toda a área de trabalho +Name[sk]=Zachytiť celú plochu Name[sl]=Zajemi celotno namizje Name[sv]=Ta en bild av hela skrivbordet Name[uk]=Захопити зображення усієї стільниці Name[x-test]=xxCapture Entire Desktopxx Name[zh_CN]=捕获整个桌面 Exec=spectacle -f [Desktop Action CurrentMonitorScreenShot] Name=Capture Current Monitor Name[ar]=التقط الشّاشة الحاليّة Name[ast]=Capturar monitor actual Name[ca]=Captura el monitor actual Name[ca@valencia]=Captura el monitor actual Name[de]=Den aktuellen Bildschirm aufnehmen +Name[el]=Λήψη της τρέχουσας οθόνης Name[en_GB]=Capture Current Monitor Name[es]=Capturar el monitor actual Name[fi]=Kaappaa nykyinen näyttö Name[gl]=Capturar o monitor actual Name[it]=Cattura il monitor attuale Name[ko]=현재 모니터 찍기 Name[lt]=Paveiksluoti šio monitoriaus vaizdą Name[nl]=Huidige monitor opnemen Name[nn]=Ta bilete av gjeldande skjerm Name[pl]=Przechwyć bieżący monitor Name[pt]=Capturar o Monitor Actual Name[pt_BR]=Capturar do monitor atual +Name[sk]=Zachytiť aktuálny monitor Name[sl]=Zajemi trenutni zaslon Name[sv]=Ta en bild av aktuell bildskärm Name[uk]=Захопити зображення на моніторі Name[x-test]=xxCapture Current Monitorxx Name[zh_CN]=捕获当前屏幕 Exec=spectacle -m [Desktop Action ActiveWindowScreenShot] Name=Capture Active Window Name[ar]=التقط النّافذة النّشطة Name[ast]=Capturar ventana actual Name[ca]=Captura la finestra activa Name[ca@valencia]=Captura la finestra activa Name[de]=Das aktive Fenster aufnehmen +Name[el]=Λήψη του ενεργού παραθύρου Name[en_GB]=Capture Active Window Name[es]=Capturar la ventana activa Name[fi]=Kaappaa aktiivinen ikkuna Name[gl]=Capturar a xanela activa Name[it]=Cattura la finestra attiva Name[ko]=활성 창 찍기 Name[lt]=Paveiksluoti veikiamąjį langą Name[nl]=Actieve venster opnemen Name[nn]=Ta bilete av aktivt vindauge Name[pl]=Przechwyć aktywne okno Name[pt]=Capturar a Janela Activa Name[pt_BR]=Capturar a janela ativa +Name[sk]=Zachytiť aktuálne okno Name[sl]=Zajemi dejavno okno Name[sv]=Ta en bild av aktivt fönster Name[uk]=Захопити зображення активного вікна Name[x-test]=xxCapture Active Windowxx Name[zh_CN]=捕获活动窗口 Exec=spectacle -a [Desktop Action RectangularRegionScreenShot] Name=Capture Rectangular Region Name[ar]=التقط منطقة مستطيلة Name[ast]=Capturar rexón rectangular Name[ca]=Captura una regió rectangular Name[ca@valencia]=Captura una regió rectangular Name[de]=Einen rechteckigen Bereich aufnehmen +Name[el]=Λήψη ορθογώνιας περιοχής Name[en_GB]=Capture Rectangular Region Name[es]=Capturar una región rectangular Name[fi]=Kaappaa suorakulmainen alue Name[gl]=Capturar unha rexión rectangular Name[it]=Cattura una regione rettangolare Name[ko]=화면의 사각형 영역 찍기 Name[lt]=Paveiksluoti stačiakampę ekrano sritį Name[nl]=Rechthoekig gebied opnemen Name[nn]=Ta bilete av skjermutsnitt Name[pl]=Przechwyć obszar prostokątny Name[pt]=Capturar uma Região Rectangular Name[pt_BR]=Capturar uma região retangular +Name[sk]=Zachytiť pravouhlú oblasť Name[sl]=Zajemi pravokotno območje Name[sv]=Ta en bild av ett rektangulärt område Name[uk]=Захопити прямокутну область екрана Name[x-test]=xxCapture Rectangular Regionxx Name[zh_CN]=捕获矩形区域 Exec=spectacle -r diff --git a/desktop/spectacle.khotkeys b/desktop/spectacle.khotkeys index 95939df..9b242fa 100644 --- a/desktop/spectacle.khotkeys +++ b/desktop/spectacle.khotkeys @@ -1,130 +1,414 @@ [Main] ImportId=spectacle Version=3 [Data] DataCount=1 [Data_1] Comment=Shortcuts for taking screenshots +Comment[ast]=Atayos pa facer captures +Comment[ca]=Dreceres per a prendre captures de pantalla +Comment[ca@valencia]=Dreceres per a prendre captures de pantalla +Comment[de]=Kurzbefehle für die Aufnahme von Bildschirmfotos +Comment[el]=Συντομεύσεις για τη λήψη στιγμιοτύπων οθόνης +Comment[en_GB]=Shortcuts for taking screenshots +Comment[es]=Accesos rápidos para realizar capturas de pantalla +Comment[fi]=Kuvankaappausten ottamisen pikanäppäimet +Comment[gl]=Atallos para facer capturas de pantalla. +Comment[it]=Scorciatoia per acquisire schermate +Comment[nl]=Sneltoetsen voor het maken van schermafdrukken +Comment[nn]=Snarvegar for å ta bilete av skjermen +Comment[pl]=Skróty do zrzucania ekranu +Comment[pt]=Atalhos para tirar fotografias +Comment[pt_BR]=Atalhos para capturar imagens da tela +Comment[sl]=Bližnjice za zajemanje zaslonskih slik +Comment[sv]=Genvägar för att ta skärmbilder +Comment[uk]=Клавіатурні скорочення для створення знімків +Comment[x-test]=xxShortcuts for taking screenshotsxx +Comment[zh_CN]=截图快捷键 DataCount=4 Enabled=true Name=Screenshots +Name[ast]=Captures de pantalla +Name[ca]=Captures de pantalla +Name[ca@valencia]=Captures de pantalla +Name[de]=Bildschirmfotos +Name[el]=Στιγμιότυπα οθόνης +Name[en_GB]=Screenshots +Name[es]=Capturas de pantalla +Name[fi]=Kuvankaappaukset +Name[gl]=Capturas de pantalla +Name[it]=Schermate +Name[nl]=Schermafdrukken +Name[nn]=Skjermbilete +Name[pl]=Zrzuty ekranu +Name[pt]=Fotografias +Name[pt_BR]=Captura de tela +Name[sl]=Zaslonske slike +Name[sv]=Skärmbilder +Name[uk]=Знімки екрана +Name[x-test]=xxScreenshotsxx +Name[zh_CN]=截图 SystemGroup=0 Type=ACTION_DATA_GROUP [Data_1Conditions] Comment= ConditionsCount=0 [Data_1_1] Comment=Start the screenshot tool and show the GUI +Comment[ast]=Anicia la ferramienta de captures de pantalla y amuesa la GUI +Comment[ca]=Inicia l'eina de captura de pantalla i mostra la IGU +Comment[ca@valencia]=Inicia l'eina de captura de pantalla i mostra la IGU +Comment[de]=Startet das Programm und zeigt die graphische Bedienungsoberfläche an +Comment[el]=Εκκίνηση του εργαλείου στιγμιοτύπων και εμφάνιση του γραφικού περιβάλλοντος +Comment[en_GB]=Start the screenshot tool and show the GUI +Comment[es]=Iniciar la herramienta de captura de pantalla y mostrar su interfaz +Comment[fi]=Käynnistä kuvankaappausohjelma ja näytä käyttöliittymä +Comment[gl]=Iniciar a ferramenta de captura de pantalla e mostrar a súa interface. +Comment[it]=Avvia lo strumento per le schermate e ne mostra l'interfaccia +Comment[nl]=Start het hulpmiddel voor schermafdrukken en toon de GUI +Comment[nn]=Start skjermbiletverktøyet og vis grafisk grensesnitt +Comment[pl]=Wywołaj narzędzie zrzutów ekranu i pokaż graficzny interfejs +Comment[pt]=Iniciar a ferramenta de fotografias e mostrar a interface +Comment[pt_BR]=Inicia a ferramenta gráfica para captura de telas +Comment[sl]=Zaženi orodje za zaslonske slike in prikaži grafični uporabniški vmesnik +Comment[sv]=Starta skärmbildsverktyget och visa det grafiska användargränssnittet +Comment[uk]=Запустити інструмент створення знімків і показати графічний інтерфейс +Comment[x-test]=xxStart the screenshot tool and show the GUIxx +Comment[zh_CN]=启动截图工具并显示界面 Enabled=true Name=Start Screenshot Tool +Name[ast]=Aniciar ferramienta de captures de pantalla +Name[ca]=Inicia l'eina de captura de pantalla +Name[ca@valencia]=Inicia l'eina de captura de pantalla +Name[de]=Bildschirmfoto-Programm starten +Name[el]=Εκκίνηση του εργαλείου στιγμιοτύπων +Name[en_GB]=Start Screenshot Tool +Name[es]=Iniciar la herramienta de captura de pantalla +Name[fi]=Käynnistä kuvankaappausohjelma +Name[gl]=Iniciar a ferramenta de captura de pantalla +Name[it]=Avvia lo strumento per le schermate +Name[nl]=Het hulpmiddel voor schermafdrukken starten +Name[nn]=Skjermbiletverktøyet +Name[pl]=Wywołaj narzędzie zrzutów ekranu +Name[pt]=Iniciar a Ferramenta de Fotografias +Name[pt_BR]=Iniciar a ferramenta de captura de tela +Name[sl]=Zaženi orodje za zaslonske slike +Name[sv]=Starta skärmbildsverktyg +Name[uk]=Запустити інструмент створення знімків +Name[x-test]=xxStart Screenshot Toolxx +Name[zh_CN]=启动截图工具 Type=SIMPLE_ACTION_DATA [Data_1_1Actions] ActionsCount=1 [Data_1_1Actions0] Arguments= Call=StartAgent RemoteApp=org.kde.Spectacle RemoteObj=/ Type=DBUS [Data_1_1Conditions] Comment= ConditionsCount=0 [Data_1_1Triggers] Comment=Simple_action +Comment[ast]=Aición_cenciella +Comment[ca]=Acció_senzilla +Comment[ca@valencia]=Acció_senzilla +Comment[cs]=Jednoduchá činnost +Comment[de]=Einfache Aktion +Comment[el]=Απλή_ενέργεια +Comment[en_GB]=Simple_action +Comment[es]=Acción_sencilla +Comment[fi]=Yksinkertainen_toiminto +Comment[gl]=Acción_sinxela +Comment[it]=Azione_semplice +Comment[nl]=Eenvoudige_handeling +Comment[nn]=Enkel_handling +Comment[pl]=Proste_działanie +Comment[pt]=Acção Simples +Comment[pt_BR]=Ação simples +Comment[sl]=Preprosto_dejanje +Comment[sv]=Enkel-åtgärd +Comment[uk]=Проста_дія +Comment[x-test]=xxSimple_actionxx +Comment[zh_CN]=简单动作 TriggersCount=1 [Data_1_1Triggers0] Key=Print Type=SHORTCUT [Data_1_2] Comment=Take a full screen (all monitors) screenshot and save it +Comment[ast]=Fai una captura a pantalla completa (tolos monitores) y guárdala +Comment[ca]=Pren una captura de pantalla completa (tots els monitors) i la desa +Comment[ca@valencia]=Pren una captura de pantalla completa (tots els monitors) i la guarda +Comment[de]=Bildschirmfoto des ganzen Bildschirms aller Monitore aufnehmen und speichern +Comment[el]=Λήψη ενός πλήρους στιγμιοτύπου οθόνης (όλες οι οθόνες) και αποθήκευσή του +Comment[en_GB]=Take a full screen (all monitors) screenshot and save it +Comment[es]=Hacer una captura de pantalla completa (de todos los monitores) y guardarla +Comment[fi]=Ota koko näytön (kaikkien näyttöjen) kuvankaappaus ja tallenna se +Comment[gl]=Capturar todas as pantallas e gardar a captura. +Comment[it]=Cattura una schermata a tutto schermo (tutti i monitor) e la salva +Comment[nl]=Een volledige schermafdruk maken (alle monitoren) en deze opslaan +Comment[nn]=Ta eit fullskjermbilete (alle skjermar) og lagra det +Comment[pl]=Wykonaj zrzut całego ekranu (wszystkie monitory) i zapisz go +Comment[pt]=Capturar o ecrã por inteiro (todos os monitores) e gravar a imagem +Comment[pt_BR]=Captura e salva a tela inteira (todos os monitores) +Comment[sl]=Zajemi celozaslonsko sliko (vsi zasloni) in jo shrani +Comment[sv]=Ta en skärmbild av hela skärmen (alla bildskärmar) och spara den +Comment[uk]=Зробити знімок усього екрана (усіх моніторів) і зберегти його +Comment[x-test]=xxTake a full screen (all monitors) screenshot and save itxx +Comment[zh_CN]=对全屏 (所有显示器) 截图并保存 Enabled=true Name=Take Full Screen Screenshot +Name[ast]=Facer captura de pantalla completa +Name[ca]=Pren una captura de pantalla completa +Name[ca@valencia]=Pren una captura de pantalla completa +Name[de]=Bildschirmfoto des ganzen Bildschirms aufnehmen +Name[el]=Λήψη πλήρους στιγμιοτύπου οθόνης +Name[en_GB]=Take Full Screen Screenshot +Name[es]=Hacer una captura de pantalla completa +Name[fi]=Ota koko näytön kuvankaappaus +Name[gl]=Capturar todas as pantallas +Name[it]=Cattura l'intero schermo +Name[nl]=Schermafdruk van Volledig scherm maken +Name[nn]=Ta fullskjermsbilete +Name[pl]=Wykonaj zrzut całego ekranu +Name[pt]=Tirar uma Imagem do Ecrã Inteiro +Name[pt_BR]=Capturar a tela inteira +Name[sl]=Zajemi celozaslonsko sliko +Name[sv]=Ta en skärmbild av hela skärmen +Name[uk]=Зробити знімок усього екрана +Name[x-test]=xxTake Full Screen Screenshotxx +Name[zh_CN]=对全屏截图 Type=SIMPLE_ACTION_DATA [Data_1_2Actions] ActionsCount=1 [Data_1_2Actions0] Arguments=false Call=FullScreen RemoteApp=org.kde.Spectacle RemoteObj=/ Type=DBUS [Data_1_2Conditions] Comment= ConditionsCount=0 [Data_1_2Triggers] Comment=Simple_action +Comment[ast]=Aición_cenciella +Comment[ca]=Acció_senzilla +Comment[ca@valencia]=Acció_senzilla +Comment[cs]=Jednoduchá činnost +Comment[de]=Einfache Aktion +Comment[el]=Απλή_ενέργεια +Comment[en_GB]=Simple_action +Comment[es]=Acción_sencilla +Comment[fi]=Yksinkertainen_toiminto +Comment[gl]=Acción_sinxela +Comment[it]=Azione_semplice +Comment[nl]=Eenvoudige_handeling +Comment[nn]=Enkel_handling +Comment[pl]=Proste_działanie +Comment[pt]=Acção Simples +Comment[pt_BR]=Ação simples +Comment[sl]=Preprosto_dejanje +Comment[sv]=Enkel-åtgärd +Comment[uk]=Проста_дія +Comment[x-test]=xxSimple_actionxx +Comment[zh_CN]=简单动作 TriggersCount=1 [Data_1_2Triggers0] Key=Shift+Print Type=SHORTCUT [Data_1_3] Comment=Take a screenshot of the currently active window and save it +Comment[ast]=Fai una captura de la ventana activa anguaño y guárdala +Comment[ca]=Pren una captura de pantalla de la finestra activa i la desa +Comment[ca@valencia]=Pren una captura de pantalla de la finestra activa i la guarda +Comment[de]=Erstellt ein Bildschirmfoto des aktuell aktiven Fensters und speichert es +Comment[el]=Λήψη ενός στιγμιοτύπου του ενεργού παραθύρου και αποθήκευσή του +Comment[en_GB]=Take a screenshot of the currently active window and save it +Comment[es]=Hacer una captura de la ventana activa actualmente y guardarla +Comment[fi]=Ota kuvankaappaus aktiivisesta ikkunasta ja tallenna se +Comment[gl]=Capturar a xanela activa e gardar a captura. +Comment[it]=Cattura una schermata della finestra attiva e la salva +Comment[nl]=Een schermafdruk van het huidige actieve venster maken en het opslaan +Comment[nn]=Ta skjermbilete frå det aktive vindauget og lagra det +Comment[pl]=Wykonaj zrzut obecnie aktywnego okna i zapisz go +Comment[pt]=Capturar uma imagem da janela activa de momento e gravá-la +Comment[pt_BR]=Captura e salva a imagem da janela ativa +Comment[sl]=Zajemi zaslonsko sliko trenutno dejavnega okna in jo shrani +Comment[sv]=Ta en skärmbild av nuvarande aktiva fönster och spara den +Comment[uk]=Зробити знімок поточного активного вікна і зберегти його +Comment[x-test]=xxTake a screenshot of the currently active window and save itxx +Comment[zh_CN]=对当前活动窗口截图并保存 Enabled=true Name=Take Active Window Screenshot +Name[ast]=Capturar ventana activa +Name[ca]=Pren una captura de pantalla de la finestra activa +Name[ca@valencia]=Pren una captura de pantalla de la finestra activa +Name[de]=Bildschirmfoto des aktiven Fensters +Name[el]=Λήψη στιγμιοτύπου ενεργού παραθύρου +Name[en_GB]=Take Active Window Screenshot +Name[es]=Hacer una captura de la ventana activa +Name[fi]=Ota kuvankaappaus aktiivisesta ikkunasta +Name[gl]=Capturar a xanela activa +Name[it]=Cattura la finestra attiva +Name[nl]=Schermafdruk van actieve venster maken +Name[nn]=Ta skjermbilete av aktivt vindauge +Name[pl]=Wykonaj zrzut obecnie aktywnego okna +Name[pt]=Tirar uma Fotografia da Janela Activa +Name[pt_BR]=Capturar a imagem da janela ativa +Name[sl]=Zajemi zaslonsko sliko dejavnega okna +Name[sv]=Ta en skärmbild av aktivt fönster +Name[uk]=Зробити знімок активного вікна +Name[x-test]=xxTake Active Window Screenshotxx +Name[zh_CN]=对活动窗口截图 Type=SIMPLE_ACTION_DATA [Data_1_3Actions] ActionsCount=1 [Data_1_3Actions0] Arguments=true, false Call=ActiveWindow RemoteApp=org.kde.Spectacle RemoteObj=/ Type=DBUS [Data_1_3Conditions] Comment= ConditionsCount=0 [Data_1_3Triggers] Comment=Simple_action +Comment[ast]=Aición_cenciella +Comment[ca]=Acció_senzilla +Comment[ca@valencia]=Acció_senzilla +Comment[cs]=Jednoduchá činnost +Comment[de]=Einfache Aktion +Comment[el]=Απλή_ενέργεια +Comment[en_GB]=Simple_action +Comment[es]=Acción_sencilla +Comment[fi]=Yksinkertainen_toiminto +Comment[gl]=Acción_sinxela +Comment[it]=Azione_semplice +Comment[nl]=Eenvoudige_handeling +Comment[nn]=Enkel_handling +Comment[pl]=Proste_działanie +Comment[pt]=Acção Simples +Comment[pt_BR]=Ação simples +Comment[sl]=Preprosto_dejanje +Comment[sv]=Enkel-åtgärd +Comment[uk]=Проста_дія +Comment[x-test]=xxSimple_actionxx +Comment[zh_CN]=简单动作 TriggersCount=1 [Data_1_3Triggers0] Key=Meta+Print Type=SHORTCUT [Data_1_4] Comment=Take a screenshot of a rectangular region you specify and save it +Comment[ast]=Fai una captura d'una rexón rectangular qu'especifiques y guárdala +Comment[ca]=Pren una captura de pantalla d'una regió rectangular i la desa +Comment[ca@valencia]=Pren una captura de pantalla d'una regió rectangular i la guarda +Comment[de]=Erstellt ein Bildschirmfoto eines rechteckigen Bereichs und speichert es +Comment[el]=Λήψη στιγμιοτύπου μιας καθορισμένης ορθογώνιας περιοχής και αποθήκευσή του +Comment[en_GB]=Take a screenshot of a rectangular region you specify and save it +Comment[es]=Hacer una captura de la región rectangular indicada y guardarla +Comment[fi]=Ota kuvankaappaus annetusta suorakulmaisesta alueesta ja tallenna se +Comment[gl]=Capturar unha rexión rectangular indicada e gardar a captura. +Comment[it]=Cattura una schermata di una regione rettangolare a piacere e la salva +Comment[nl]=Een schermafdruk van een rechthoekig gebied dat u specificeert maken en het opslaan +Comment[nn]=Ta rektangulært skjermutsnitt og lagra det +Comment[pl]=Wykonaj zrzut podanego przez ciebie prostokątnego obszaru i zapisz go +Comment[pt]=Capturar uma imagem de uma região rectangular à sua escolha e gravá-la +Comment[pt_BR]=Captura e salva a imagem de uma região retangular +Comment[sl]=Zajemi zaslonsko sliko pravokotnega območja in jo shrani +Comment[sv]=Ta en skärmbild av ett angivet rektangulärt område och spara den +Comment[uk]=Зробити знімок вказаної прямокутної ділянки і зберегти його +Comment[x-test]=xxTake a screenshot of a rectangular region you specify and save itxx +Comment[zh_CN]=对指定矩形区域截图并保存 Enabled=true Name=Take Rectangular Region Screenshot +Name[ast]=Capturar rexón rectangular +Name[ca]=Pren una captura de pantalla d'una regió rectangular +Name[ca@valencia]=Pren una captura de pantalla d'una regió rectangular +Name[de]=Bildschirmfoto eines rechteckigen Bereichs +Name[el]=Λήψη στιγμιοτύπου ορθογώνιας περιοχής +Name[en_GB]=Take Rectangular Region Screenshot +Name[es]=Hacer una captura de una región rectangular +Name[fi]=Ota kuvankaappaus suorakulmaisesta alueesta +Name[gl]=Capturar unha rexión rectangular +Name[it]=Cattura una regione rettangolare +Name[nl]=Schermafdruk van rechthoekig gebied maken +Name[nn]=Ta rektangulært skjermutsnitt +Name[pl]=Wykonaj zrzut prostokątnego obszaru +Name[pt]=Tirar uma Fotografia de uma Região Rectangular +Name[pt_BR]=Capturar a imagem de uma região retangular +Name[sl]=Zajemi zaslonsko sliko pravokotnega območja +Name[sv]=Ta en skärmbild av ett rektangulärt område +Name[uk]=Зробити знімок прямокутної ділянки +Name[x-test]=xxTake Rectangular Region Screenshotxx +Name[zh_CN]=对矩形区域截图 Type=SIMPLE_ACTION_DATA [Data_1_4Actions] ActionsCount=1 [Data_1_4Actions0] Arguments=true Call=RectangularRegion RemoteApp=org.kde.Spectacle RemoteObj=/ Type=DBUS [Data_1_4Conditions] Comment= ConditionsCount=0 [Data_1_4Triggers] Comment=Simple_action +Comment[ast]=Aición_cenciella +Comment[ca]=Acció_senzilla +Comment[ca@valencia]=Acció_senzilla +Comment[cs]=Jednoduchá činnost +Comment[de]=Einfache Aktion +Comment[el]=Απλή_ενέργεια +Comment[en_GB]=Simple_action +Comment[es]=Acción_sencilla +Comment[fi]=Yksinkertainen_toiminto +Comment[gl]=Acción_sinxela +Comment[it]=Azione_semplice +Comment[nl]=Eenvoudige_handeling +Comment[nn]=Enkel_handling +Comment[pl]=Proste_działanie +Comment[pt]=Acção Simples +Comment[pt_BR]=Ação simples +Comment[sl]=Preprosto_dejanje +Comment[sv]=Enkel-åtgärd +Comment[uk]=Проста_дія +Comment[x-test]=xxSimple_actionxx +Comment[zh_CN]=简单动作 TriggersCount=1 [Data_1_4Triggers0] Key=Meta+Shift+Print -Type=SHORTCUT \ No newline at end of file +Type=SHORTCUT diff --git a/desktop/spectacle.notifyrc b/desktop/spectacle.notifyrc index e31c9d4..dd9b445 100644 --- a/desktop/spectacle.notifyrc +++ b/desktop/spectacle.notifyrc @@ -1,106 +1,110 @@ [Global] IconName=spectacle Name=Spectacle Name[ast]=Spectacle Name[ca]=Spectacle Name[ca@valencia]=Spectacle Name[da]=Spectacle Name[de]=Spectacle +Name[el]=Spectacle Name[en_GB]=Spectacle Name[es]=Spectacle Name[fi]=Spectacle Name[gl]=Spectacle Name[it]=Spectacle Name[ko]=Spectacle Name[lt]=Spectacle Name[nl]=Spectacle Name[nn]=Spectacle Name[pl]=Spectacle Name[pt]=Spectacle Name[pt_BR]=Spectacle Name[ru]=Spectacle Name[sk]=Spectacle Name[sl]=Spectacle Name[sv]=Spectacle Name[uk]=Spectacle Name[x-test]=xxSpectaclexx Name[zh_CN]=Spectacle Comment=Screenshot Capture Utility Comment[ar]=أداة لأخذ اللقطات Comment[ast]=Utilidá de captura de pantalla -Comment[ca]=Utilitat per pendre captures de pantalla -Comment[ca@valencia]=Utilitat per pendre captures de pantalla +Comment[ca]=Utilitat per prendre captures de pantalla +Comment[ca@valencia]=Utilitat per prendre captures de pantalla Comment[cs]=Nástroj na snímání obrazovky Comment[de]=Dienstprogramm für Bildschirmfotos +Comment[el]=Εργαλείο λήψης στιγμιοτύπων οθόνης Comment[en_GB]=Screenshot Capture Utility Comment[es]=Utilidad de capturas de pantalla Comment[fi]=Kuvankaappausohjelma Comment[gl]=Utilidade para facer capturas de pantalla. Comment[it]=Accessorio per catturare schermate Comment[ko]=화면 캡처 유틸리티 Comment[lt]=KDE ekranvaizdžių paveikslavimo programa Comment[nl]=Hulpmiddel voor het maken van een schermafdruk Comment[nn]=Verktøy for å lagra skjermbilete Comment[pl]=Narzędzie do przechwytywania ekranu Comment[pt]=Utilitário de Captura de Imagens Comment[pt_BR]=Utilitário de captura de tela Comment[ru]=Программа для создания снимков экрана Comment[sk]=Nástroj na zachytávanie obrazovky Comment[sl]=Pripomoček za zajem zaslona Comment[sv]=Verktyg för att ta skärmbilder Comment[uk]=Програма для створення знімків екрана Comment[x-test]=xxScreenshot Capture Utilityxx Comment[zh_CN]=屏幕捕获工具 [Event/newScreenshotSaved] Name=New Screenshot Saved Name[ar]=حُفظت لقطة جديدة Name[ast]=Guardóse una captura nueva Name[ca]=S'ha desat una captura de pantalla nova Name[ca@valencia]=S'ha guardat una captura de pantalla nova Name[de]=Neues Bildschirmfoto gespeichert +Name[el]=Αποθηκεύτηκε νέο στιγμιότυπο Name[en_GB]=New Screenshot Saved Name[es]=Nueva captura de pantalla guardada Name[fi]=Uusi kuvankaappaus tallennettu Name[gl]=Gardouse a nova captura Name[it]=Nuova schermata salvata Name[ko]=새 스크린샷 저장됨 Name[lt]=Ekranvaizdis įrašytas Name[nl]=Nieuwe schermafdruk opgeslagen Name[nn]=Nytt skjermbilete lagra Name[pl]=Zapisano nowy zrzut ekranu Name[pt]=Nova Imagem Gravada Name[pt_BR]=Nova captura de tela salva Name[ru]=Сохранён новый снимок экрана Name[sk]=Nová obrazovka uložená Name[sl]=Shranjena nova zaslonska slika Name[sv]=Ny skärmbild sparad Name[uk]=Збереженого нових знімок Name[x-test]=xxNew Screenshot Savedxx Name[zh_CN]=新屏幕截图已保存 Comment=A new screenshot was captured and saved Comment[ar]=أُخذت لقطة جديدة وحُفظت Comment[ast]=Capturóse y guardóse una captura de pantalla nueva Comment[ca]=S'ha efectuat una captura de pantalla nova i s'ha desat Comment[ca@valencia]=S'ha efectuat una captura de pantalla nova i s'ha guardat Comment[de]=Ein neues Bildschirmfoto wurde aufgenommen und gespeichert +Comment[el]=Λήφθηκε και αποθηκεύτηκε ένα νέο στιγμιότυπο Comment[en_GB]=A new screenshot was captured and saved Comment[es]=Se ha hecho y se ha guardado una nueva captura de pantalla Comment[fi]=Uusi kuva kaapattiin ja tallennettiin Comment[gl]=Fíxose unha nova captura de pantalla e gardouse. Comment[it]=Una nuova schermata è stata catturata e salvata Comment[ko]=새 스크린샷을 캡처하여 저장함 Comment[lt]=Ekranas nupaveiksluotas ir įrašytas Comment[nl]=Een nieuwe schermafdruk is opgenomen en opgeslagen Comment[nn]=Tok og lagra nytt skjermbilete Comment[pl]=Przechwycono i zapisano zawartość ekranu Comment[pt]=Foi capturada e gravada uma nova imagem Comment[pt_BR]=Uma nova imagem foi obtida e salva Comment[ru]=Новый снимок экрана создан и сохранён. Comment[sk]=Nová obrazovka bola zachytená a uložená Comment[sl]=Zajeta in shranjena je bila nova zaslonska slika Comment[sv]=En ny skärmbild togs och sparades Comment[uk]=Було створено і збережено новий знімок Comment[x-test]=xxA new screenshot was captured and savedxx Comment[zh_CN]=屏幕截图已截取并保存 Action=Popup diff --git a/doc/index.docbook b/doc/index.docbook index 40e87d6..e4dce83 100644 --- a/doc/index.docbook +++ b/doc/index.docbook @@ -1,278 +1,277 @@ - BoudhayanGupta"> bgupta@kde.org"> ]> The &spectacle; Handbook &Boudhayan.Gupta; &Boudhayan.Gupta.mail; &Boudhayan.Gupta; &Boudhayan.Gupta.mail; 1997-2000&Richard.J.Moore; 2000&Matthias.Ettrich; 2015&Boudhayan.Gupta; &FDLNotice; 2015-10-17 - 15.12.0 + Applications 15.12 &spectacle; is a simple application for capturing desktop screenshots. It can capture images of the entire desktop, a single monitor, the currently active window, the window currently under the mouse, or a rectangular region of the screen. The images can then be printed, sent to other applications for manipulation, or quickly be saved as-is. KDE &spectacle; kdegraphics screenshot screen capture screen grab Introduction &spectacle; is a simple application for capturing desktop screenshots. It can capture images of the entire desktop, a single monitor, the currently active window, the window currently under the mouse, or a rectangular region of the screen. The images can then be printed, sent to other applications for manipulation, or quickly be saved as-is. Please report any problems or feature requests to the &kde; Bug Tracking System. Starting &spectacle; &spectacle; can be started in a variety of ways, as described below: In the application launcher menu, &spectacle; can be found at ApplicationsGraphicsScreenshot Capture Utility &spectacle; Pressing the Print Screen button on the keyboard will immediately launch &spectacle;. Additionally, two more keyboard shortcuts are available. Pressing MetaPrint Screen will take a screenshot of the active window and save it in your default save directory without showing the GUI, while pressing &Shift;Print Screen will take a screenshot of your entire desktop and save it in your default save directory without showing the GUI. You can configure the default save location and filename by starting &spectacle; normally, clicking the downward arrow beside the Save & Exit button, and selecting Configure Save Options from the menu. The mini command line &krunner; (invoked with &Alt;F2) may also be used to start &spectacle;. &spectacle; can be started from the command-line. &spectacle; has an extensive set of command-line options, including a background mode which can be used to script the capture of screenshots without showing the GUI or requiring user interaction. To start &spectacle; from the command prompt, type in: % spectacle & To view the full list of command-line options and their explanation, type in: % spectacle --help Using &spectacle; Once &spectacle; starts, you will see a window like the following: &spectacle; Main Window &spectacle; 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 &spectacle; is displayed in the preview window, which is located on the left-hand side of the &spectacle; application window. To quickly save the image and quit &spectacle; press the Save & Exit (&Ctrl;Q) button. By default, this saves the image as a PNG file in your default Pictures folder, and exits the application immediately. The default save location and filename can be configured, as described later. The image can be saved by clicking on the arrow portion of the Save & Exit button, and choosing Save As... (&Ctrl;&Shift;S) option. This opens the standard &kde; save dialog, where you can choose the filename, the folder location, and the format that your screenshot will be saved in. You may edit the filename to anything you wish, including the name of a previously saved screenshot. You may also select the Save... (&Ctrl;S) option, which will save the image to its default location and with the default filename. Taking A Screenshot To discard the current screenshot and take another screenshot, press the Take a New Screenshot (&Ctrl;N) button. You may configure certain options on the right hand side of the application window before taking a new screenshot. These options allow you to select the area of the screen that is to be captured, set a delay before capturing the image, and configure whether the mouse cursor and/or the window decorations should be captured along with the screenshot. Capture Mode The capture mode settings allow you to set the area of the screen that should be captured, and whether there should be a delay between pressing the Take a New Screenshot (&Ctrl;N) button and taking the screenshot. You may also enable the On Click checkbox, which disables the delay function and only takes the screenshot after you click anywhere on the screen after clicking the Take a New Screenshot (&Ctrl;N) button. The Area combo-box allows you to set the area of the screen that should be captured. There are five options to select from, as described below. The Full Screen (All Monitors) option takes a screenshot of your entire desktop, spread across all the outputs, including all the monitors, projectors etc. The Current Screen option takes a screenshot of the output that currently contains the mouse pointer. The Active Window option takes a screenshot of the window that currently has focus. It is advisable to use a delay with this mode, to give you time to select and activate a window before the screenshot is taken. The Window Under Cursor option takes a screenshot of the window that is under the mouse cursor. If the cursor is on top of a pop-up menu, &spectacle; tries to take a screenshot of the menu as well as its parent window. While this works most of the time, in certain cases it may fail to obtain information about the parent window. In this case, &spectacle; falls back to old way of capturing the image automatically, and captures an image of only the pop-up menu. You can also force the old way of capturing the image by checking the Capture the current pop-up only checkbox under Content Options The Rectangular Region option allows you to select a rectangular region of your desktop with your mouse. This region may be spread across different outputs. This mode does not immediately take a screenshot but allows you to draw a rectangle on your screen, which can be moved and resized as needed. Once the desired selection rectangle has been drawn, double-clicking anywhere on the screen, or pressing the &Enter; button on the keyboard will capture the screenshot. The Delay spin-box allows you to set the delay between pressing the Take a New Screenshot (&Ctrl;N) button and taking the screenshot. This delay can be set in increments of 0.1 seconds, or 100 milliseconds. Enabling the On Click checkbox overrides the delay. When this checkbox is enabled, pressing the Take a New Screenshot (&Ctrl;N) button hides the &spectacle; window and changes the mouse cursor to a crosshair. The screenshot is captured when the mouse is left-clicked, or aborted if any other mouse buttons are clicked. Note that you cannot interact with the desktop using the mouse while the cursor is a crosshair, but you can use the keyboard. Content Options The content options settings allow you to select whether the mouse cursor should be included in the screenshots, and whether to capture window decorations along with the image of a single application window. In Window Under Cursor mode, it also allows you to select if &spectacle; shall only capture the image of the current pop-up menu under the cursor, or also include the parent window. Enabling the Include mouse pointer checkbox includes an image of the mouse pointer in the screenshot. The Include window titlebar and borders option is only enabled when either the Active Window mode or the Window Under Cursor mode is selected in the Capture Area combo-box. Checking this option includes the window borders and decoration in the screenshot, while unchecking it gives an image of only the window contents. The Capture the current pop-up only option is only enabled when the Window Under Cursor mode is selected in the Area combo-box. Checking this option captures only the pop-up menu under the cursor, without its parent window. Additional Functionality Buttons There are five buttons located at the bottom of the &spectacle; window. Their functions are described below: Help This button gives you a menu where you can open the &spectacle; Handbook, report a bug, switch the language for &spectacle; or get some more information About &spectacle; and About &kde;. Open With... This drop-down menu will allow you to directly open the screenshot with all programs that are associated with the 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 e-mail your screenshots and export them directly to some social networks and websites. Copy To Clipboard This button copies the current screenshot to the clipboard. You can also use the &Ctrl;C keyboard shortcut for this. Save & Exit Clicking this button saves the screenshot as a PNG image in your default Pictures directory and immediately exits the application. Additionally, if you click the arrow on the right side of the button, a drop-down menu allows you to simply Save the image, save the image with a different filename, location and format (Save As...), Print the image, and configure the default save options, such as where to save the image by default and what filename to save it as. Discard Discards the screenshot and immediately exits the application. Configure Save Options When you use the Save & Exit or the Save functions, &spectacle; saves the image with a default filename, in your Pictures folder under your home directory. The default filename includes the date and time when the image was taken. The Configure Save Options option allows you to set the default save location and filename. Clicking this option brings up a dialog box like the following: Configure Save Options The dialog box includes appropriate help text on how to configure the save options. 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. Credits and License Program copyright © 2015 &Boudhayan.Gupta; &Boudhayan.Gupta.mail;. Portions of the code are based directly on code from the &ksnapshot; project. Copyright © 1997-2011 The &ksnapshot; Developers. Detailed copyright assignment notices are available in the headers in the source code. Portions of the code are based directly on code from the &kwin; project. Copyright © 2008, 2013 The &kwin; Developers. Detailed copyright assignment notices are available in the headers in the source code. Documentation based on the original &ksnapshot; documentation: Copyright © 1997-2000 &Richard.J.Moore; &Richard.J.Moore.mail; Copyright © 2000 &Matthias.Ettrich; &Matthias.Ettrich.mail; &underFDL; &underGPL; &documentation.index; diff --git a/src/Config.h.in b/src/Config.h.in index 158ca57..cca2812 100644 --- a/src/Config.h.in +++ b/src/Config.h.in @@ -1,19 +1,19 @@ #ifndef SPECTACLE_CONFIG_H #define SPECTACLE_CONFIG_H /* Define to 1 if we are building with XCB */ #cmakedefine XCB_FOUND 1 /* Define to 1 if we have KIPI */ #cmakedefine KIPI_FOUND 1 /* Define to 1 if we have Purpose */ #cmakedefine PURPOSE_FOUND 1 /* Set the Spectacle version from CMake */ #cmakedefine SPECTACLE_VERSION "@SPECTACLE_VERSION@" /* set the release codename */ -#define SPECTACLE_CODENAME "So Much For Subtlety" +#define SPECTACLE_CODENAME "Sorry For Any Inconvenience" #endif diff --git a/src/ExportManager.cpp b/src/ExportManager.cpp index d703f22..763b1f3 100644 --- a/src/ExportManager.cpp +++ b/src/ExportManager.cpp @@ -1,363 +1,366 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * 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 "ExportManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SpectacleConfig.h" ExportManager::ExportManager(QObject *parent) : QObject(parent), mSavePixmap(QPixmap()), mTempFile(QUrl()) {} ExportManager::~ExportManager() {} ExportManager* ExportManager::instance() { static ExportManager instance; return &instance; } // screenshot pixmap setter and getter QPixmap ExportManager::pixmap() const { return mSavePixmap; } QString ExportManager::pixmapDataUri() const { QImage image = mSavePixmap.toImage(); QByteArray imageData; // write the image into the QByteArray using a QBuffer { QBuffer dataBuf(&imageData); dataBuf.open(QBuffer::WriteOnly); image.save(&dataBuf, "PNG"); } // compose the data uri and return it QString uri = QStringLiteral("data:image/png;base64,") + QString::fromLatin1(imageData.toBase64()); return uri; } void ExportManager::setPixmap(const QPixmap &pixmap) { mSavePixmap = pixmap; // reset our saved tempfile if (mTempFile.isValid()) { QFile file(mTempFile.toLocalFile()); file.remove(); mTempFile = QUrl(); } } // native file save helpers QString ExportManager::saveLocation() const { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup generalConfig = KConfigGroup(config, "General"); QString savePath = generalConfig.readPathEntry( "default-save-location", QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); if (savePath.isEmpty() || savePath.isNull()) { savePath = QDir::homePath(); } savePath = QDir::cleanPath(savePath); QDir savePathDir(savePath); if (!(savePathDir.exists())) { savePathDir.mkpath(QStringLiteral(".")); generalConfig.writePathEntry("last-saved-to", savePath); } return savePath; } void ExportManager::setSaveLocation(const QString &savePath) { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup generalConfig = KConfigGroup(config, "General"); generalConfig.writePathEntry("last-saved-to", savePath); } QUrl ExportManager::getAutosaveFilename() { const QString baseDir = saveLocation(); const QDir baseDirPath(baseDir); const QString filename = makeAutosaveFilename(); const QString fullpath = autoIncrementFilename(baseDirPath.filePath(filename), QStringLiteral("png")); const QUrl fileNameUrl = QUrl::fromUserInput(fullpath); if (fileNameUrl.isValid()) { return fileNameUrl; } else { return QUrl(); } } QString ExportManager::makeAutosaveFilename() { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup generalConfig = KConfigGroup(config, "General"); const QDateTime timestamp = QDateTime::currentDateTime(); QString baseName = generalConfig.readEntry("save-filename-format", "Screenshot_%Y%M%D_%H%m%S"); return baseName.replace(QLatin1String("%Y"), timestamp.toString(QStringLiteral("yyyy"))) .replace(QLatin1String("%y"), timestamp.toString(QStringLiteral("yy"))) .replace(QLatin1String("%M"), timestamp.toString(QStringLiteral("MM"))) .replace(QLatin1String("%D"), timestamp.toString(QStringLiteral("dd"))) .replace(QLatin1String("%H"), timestamp.toString(QStringLiteral("hh"))) .replace(QLatin1String("%m"), timestamp.toString(QStringLiteral("mm"))) .replace(QLatin1String("%S"), timestamp.toString(QStringLiteral("ss"))); } QString ExportManager::autoIncrementFilename(const QString &baseName, const QString &extension) { if (!(isFileExists(QUrl::fromUserInput(baseName + '.' + extension)))) { return baseName + '.' + extension; } QString fileNameFmt(baseName + "-%1." + extension); for (quint64 i = 1; i < std::numeric_limits::max(); i++) { if (!(isFileExists(QUrl::fromUserInput(fileNameFmt.arg(i))))) { return fileNameFmt.arg(i); } } // unlikely this will ever happen, but just in case we've run // out of numbers return fileNameFmt.arg("OVERFLOW-" + QString::number(qrand() % 10000)); } QString ExportManager::makeSaveMimetype(const QUrl &url) { QMimeDatabase mimedb; QString type = mimedb.mimeTypeForUrl(url).preferredSuffix(); if (type.isEmpty()) { return QStringLiteral("png"); } return type; } bool ExportManager::writeImage(QIODevice *device, const QByteArray &format) { QImageWriter imageWriter(device, format); if (!(imageWriter.canWrite())) { emit errorMessage(i18n("QImageWriter cannot write image: ") + imageWriter.errorString()); return false; } return imageWriter.write(mSavePixmap.toImage()); } bool ExportManager::localSave(const QUrl &url, const QString &mimetype) { QFile outputFile(url.toLocalFile()); outputFile.open(QFile::WriteOnly); if(!writeImage(&outputFile, mimetype.toLatin1())) { emit errorMessage(i18n("Cannot save screenshot. Error while writing file.")); return false; } return true; } bool ExportManager::remoteSave(const QUrl &url, const QString &mimetype) { QTemporaryFile tmpFile; if (tmpFile.open()) { if(!writeImage(&tmpFile, mimetype.toLatin1())) { emit errorMessage(i18n("Cannot save screenshot. Error while writing temporary local file.")); return false; } KIO::FileCopyJob *uploadJob = KIO::file_copy(QUrl::fromLocalFile(tmpFile.fileName()), url); uploadJob->exec(); if (uploadJob->error() != KJob::NoError) { emit errorMessage(i18n("Unable to save image. Could not upload file to remote location.")); return false; } return true; } return false; } QUrl ExportManager::tempSave(const QString &mimetype) { // if we already have a temp file saved, use that if (mTempFile.isValid()) { if (QFile(mTempFile.toLocalFile()).exists()) { return mTempFile; } } QTemporaryFile tmpFile(QDir::tempPath() + QDir::separator() + "Spectacle.XXXXXX." + mimetype); tmpFile.setAutoRemove(false); tmpFile.setPermissions(QFile::ReadUser | QFile::WriteUser); if (tmpFile.open()) { if(!writeImage(&tmpFile, mimetype.toLatin1())) { emit errorMessage(i18n("Cannot save screenshot. Error while writing temporary local file.")); return QUrl(); } mTempFile = QUrl::fromLocalFile(tmpFile.fileName()); return mTempFile; } return QUrl(); } bool ExportManager::save(const QUrl &url) { if (!(url.isValid())) { emit errorMessage(i18n("Cannot save screenshot. The save filename is invalid.")); return false; } QString mimetype = makeSaveMimetype(url); if (url.isLocalFile()) { return localSave(url, mimetype); } return remoteSave(url, mimetype); } bool ExportManager::isFileExists(const QUrl &url) { if (!(url.isValid())) { return false; } KIO::StatJob * existsJob = KIO::stat(url, KIO::StatJob::DestinationSide, 0); existsJob->exec(); return (existsJob->error() == KJob::NoError); } // save slots -void ExportManager::doSave(const QUrl &url) +void ExportManager::doSave(const QUrl &url, bool notify) { if (mSavePixmap.isNull()) { emit errorMessage(i18n("Cannot save an empty screenshot image.")); return; } QUrl savePath = url.isValid() ? url : getAutosaveFilename(); if (save(savePath)) { QDir dir(savePath.path()); dir.cdUp(); setSaveLocation(dir.absolutePath()); emit imageSaved(savePath); + if (notify) { + emit forceNotify(savePath); + } } } void ExportManager::doSaveAs(QWidget *parentWindow) { QStringList supportedFilters; QMimeDatabase db; SpectacleConfig *config = SpectacleConfig::instance(); // construct the supported mimetype list Q_FOREACH (auto mimeType, QImageWriter::supportedMimeTypes()) { supportedFilters.append(QString::fromUtf8(mimeType).trimmed()); } // construct the file name QFileDialog dialog(parentWindow); dialog.setOption(QFileDialog::DontUseNativeDialog); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setFileMode(QFileDialog::AnyFile); dialog.setDirectoryUrl(config->lastSaveAsLocation()); dialog.selectFile(makeAutosaveFilename() + QStringLiteral(".png")); dialog.setDefaultSuffix(QStringLiteral(".png")); dialog.setMimeTypeFilters(supportedFilters); dialog.selectMimeTypeFilter(QStringLiteral("image/png")); // launch the dialog if (dialog.exec() == QFileDialog::Accepted) { const QUrl saveUrl = dialog.selectedUrls().first(); if (saveUrl.isValid()) { if (save(saveUrl)) { emit imageSaved(saveUrl); config->setLastSaveAsLocation(saveUrl.adjusted(QUrl::RemoveFilename)); } } } } // misc helpers void ExportManager::doCopyToClipboard() { QApplication::clipboard()->setPixmap(mSavePixmap, QClipboard::Clipboard); } void ExportManager::doPrint(QPrinter *printer) { QPainter painter; if (!(painter.begin(printer))) { emit errorMessage(i18n("Printing failed. The printer failed to initialize.")); delete printer; return; } QRect devRect(0, 0, printer->width(), printer->height()); QPixmap pixmap = mSavePixmap.scaled(devRect.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); QRect srcRect = pixmap.rect(); srcRect.moveCenter(devRect.center()); painter.drawPixmap(srcRect.topLeft(), pixmap); painter.end(); delete printer; return; } diff --git a/src/ExportManager.h b/src/ExportManager.h index b046e83..39c3bc0 100644 --- a/src/ExportManager.h +++ b/src/ExportManager.h @@ -1,92 +1,93 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * 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 EXPORTMANAGER_H #define EXPORTMANAGER_H #include #include #include #include #include class ExportManager : public QObject { Q_OBJECT // singleton-ize the class public: static ExportManager* instance(); private: explicit ExportManager(QObject *parent = 0); virtual ~ExportManager(); ExportManager(ExportManager const&) = delete; void operator= (ExportManager const&) = delete; // now the usual stuff public: Q_PROPERTY(QString saveLocation READ saveLocation WRITE setSaveLocation NOTIFY saveLocationChanged) Q_PROPERTY(QPixmap pixmap READ pixmap WRITE setPixmap NOTIFY pixmapChanged) void setSaveLocation(const QString &location); QString saveLocation() const; void setPixmap(const QPixmap &pixmap); QPixmap pixmap() const; QString pixmapDataUri() const; signals: void errorMessage(const QString &str); void saveLocationChanged(const QString &location); void pixmapChanged(const QPixmap &pixmap); void imageSaved(const QUrl &savedAt); + void forceNotify(const QUrl &savedAt); public slots: QUrl getAutosaveFilename(); QUrl tempSave(const QString &mimetype = "png"); - void doSave(const QUrl &url = QUrl()); + void doSave(const QUrl &url = QUrl(), bool notify = false); void doSaveAs(QWidget *parentWindow = 0); void doCopyToClipboard(); void doPrint(QPrinter *printer); private: QString makeAutosaveFilename(); QString autoIncrementFilename(const QString &baseName, const QString &extension); QString makeSaveMimetype(const QUrl &url); bool writeImage(QIODevice *device, const QByteArray &format); bool save(const QUrl &url); bool localSave(const QUrl &url, const QString &mimetype); bool remoteSave(const QUrl &url, const QString &mimetype); bool isFileExists(const QUrl &url); QPixmap mSavePixmap; QUrl mTempFile; }; #endif // EXPORTMANAGER_H diff --git a/src/Gui/ExportMenu.cpp b/src/Gui/ExportMenu.cpp index 0232f4e..50333e0 100644 --- a/src/Gui/ExportMenu.cpp +++ b/src/Gui/ExportMenu.cpp @@ -1,191 +1,191 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * 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 "ExportMenu.h" #include #include #include #include #include #include #include #include #include #include #include #include "Config.h" #ifdef KIPI_FOUND #include #include #include "KipiInterface/KSGKipiInterface.h" #endif ExportMenu::ExportMenu(QWidget *parent) : QMenu(parent), #ifdef PURPOSE_FOUND mPurposeMenu(new Purpose::Menu(this)), #endif mExportManager(ExportManager::instance()) { QTimer::singleShot(300, this, &ExportMenu::populateMenu); } void ExportMenu::populateMenu() { #ifdef PURPOSE_FOUND loadPurposeMenu(); #endif #ifdef KIPI_FOUND mKipiMenu = addMenu(i18n("More Online Services")); mKipiMenu->addAction(i18n("Please wait...")); mKipiMenuLoaded = false; connect(mKipiMenu, &QMenu::aboutToShow, this, &ExportMenu::loadKipiItems); #endif addSeparator(); getKServiceItems(); } void ExportMenu::imageUpdated(const QString &dataUri) { #ifdef PURPOSE_FOUND mPurposeMenu->model()->setInputData(QJsonObject { { QStringLiteral("mimeType"), QStringLiteral("image/png") }, { QStringLiteral("urls"), QJsonArray({ dataUri }) } }); mPurposeMenu->model()->setPluginType("Export"); mPurposeMenu->reload(); #else Q_UNUSED(dataUri); #endif } void ExportMenu::getKServiceItems() { // populate all locally installed applications and services // which can handle images first const KService::List services = KMimeTypeTrader::self()->query(QStringLiteral("image/png")); Q_FOREACH (auto service, services) { QString name = service->name().replace('&', QLatin1String("&&")); QAction *action = new QAction(QIcon::fromTheme(service->icon()), name, nullptr); connect(action, &QAction::triggered, [=]() { QList whereIs({ mExportManager->tempSave() }); KRun::runService(*service, whereIs, parentWidget(), true); }); addAction(action); } // now let the user manually chose an application to open the // image with addSeparator(); QAction *openWith = new QAction(this); openWith->setText(i18n("Other Application")); openWith->setIcon(QIcon::fromTheme(QStringLiteral("application-x-executable"))); openWith->setShortcuts(KStandardShortcut::open()); connect(openWith, &QAction::triggered, [=]() { QList whereIs({ mExportManager->tempSave() }); KRun::displayOpenWithDialog(whereIs, parentWidget(), true); }); addAction(openWith); } #ifdef KIPI_FOUND void ExportMenu::loadKipiItems() { if (!mKipiMenuLoaded) { QTimer::singleShot(500, this, &ExportMenu::getKipiItems); } } void ExportMenu::getKipiItems() { mKipiMenu->clear(); mKipiInterface = new KSGKipiInterface(this); KIPI::PluginLoader *loader = new KIPI::PluginLoader; loader->setInterface(mKipiInterface); loader->init(); KIPI::PluginLoader::PluginList pluginList = loader->pluginList(); - for (auto pluginInfo: pluginList) { + Q_FOREACH (const auto &pluginInfo, pluginList) { if (!(pluginInfo->shouldLoad())) { continue; } KIPI::Plugin *plugin = pluginInfo->plugin(); if (!(plugin)) { qWarning() << i18n("KIPI plugin from library %1 failed to load", pluginInfo->library()); continue; } plugin->setup(&mDummyWidget); QList actions = plugin->actions(); QSet exportActions; - for (auto action: actions) { + Q_FOREACH (auto action, actions) { KIPI::Category category = plugin->category(action); if (category == KIPI::ExportPlugin) { exportActions += action; } else if (category == KIPI::ImagesPlugin && pluginInfo->library().contains("kipiplugin_sendimages")) { exportActions += action; } } - for (auto action: exportActions) { + Q_FOREACH (auto action, exportActions) { mKipiMenu->addAction(action); } } mKipiMenuLoaded = true; } #endif #ifdef PURPOSE_FOUND void ExportMenu::loadPurposeMenu() { // attach the menu QAction *purposeMenu = addMenu(mPurposeMenu); purposeMenu->setText(i18n("Share")); // set up the callback signal connect(mPurposeMenu, &Purpose::Menu::finished, this, [this](const QJsonObject &output, int error, const QString &message) { if (error) { emit imageShared(true, message); } else { emit imageShared(false, output["url"].toString()); } }); } #endif diff --git a/src/Gui/KSMainWindow.cpp b/src/Gui/KSMainWindow.cpp index 0f3fb28..04cd5a3 100644 --- a/src/Gui/KSMainWindow.cpp +++ b/src/Gui/KSMainWindow.cpp @@ -1,329 +1,330 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * 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 "KSMainWindow.h" #include "Config.h" #include #include #include #include #include #include #ifdef XCB_FOUND #include #include #endif #include #include #include #include #include #include #include "SettingsDialog/SettingsDialog.h" #include "ExportMenu.h" #include "ExportManager.h" #include "SpectacleConfig.h" KSMainWindow::KSMainWindow(bool onClickAvailable, QWidget *parent) : QDialog(parent), mKSWidget(new KSWidget), mDivider(new QFrame), mDialogButtonBox(new QDialogButtonBox), mSendToButton(new QPushButton), mClipboardButton(new QToolButton), mSaveButton(new QToolButton), mSaveMenu(new QMenu), mMessageWidget(new KMessageWidget), mExportMenu(new ExportMenu(this)), mOnClickAvailable(onClickAvailable) { // before we do anything, we need to set a window property // that skips the close/hide window animation on kwin. this // fixes a ghost image of the spectacle window that appears // on subsequent screenshots taken with the take new screenshot // button // // credits for this goes to Thomas Lübking #ifdef XCB_FOUND if (qApp->platformName() == QStringLiteral("xcb")) { // create a window if we haven't already. note that the QWidget constructor // should already have done this if (winId() == 0) { create(0, true, true); } // do the xcb shenanigans xcb_connection_t *xcbConn = QX11Info::connection(); const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_SKIP_CLOSE_ANIMATION"); xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(xcbConn, false, effectName.length(), effectName.constData()); QScopedPointer atom(xcb_intern_atom_reply(xcbConn, atomCookie, nullptr)); if (atom.isNull()) { goto done; } uint32_t value = 1; xcb_change_property(xcbConn, XCB_PROP_MODE_REPLACE, winId(), atom->atom, XCB_ATOM_CARDINAL, 32, 1, &value); } #endif done: QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); } KSMainWindow::~KSMainWindow() {} // GUI init void KSMainWindow::init() { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup guiConfig(config, "GuiConfig"); // window properties setMinimumSize(840, 420); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); resize(minimumSize()); QPoint location = guiConfig.readEntry("window-position", QPoint(50, 50)); move(location); // change window title on save connect(ExportManager::instance(), &ExportManager::imageSaved, this, &KSMainWindow::setScreenshotWindowTitle); // the KSGWidget connect(mKSWidget, &KSWidget::newScreenshotRequest, this, &KSMainWindow::captureScreenshot); connect(mKSWidget, &KSWidget::dragInitiated, this, &KSMainWindow::dragAndDropRequest); // the Button Bar mDialogButtonBox->setStandardButtons(QDialogButtonBox::Help | QDialogButtonBox::Discard); KGuiItem::assign(mSendToButton, KGuiItem(i18n("Export Image..."))); mSendToButton->setIcon(QIcon::fromTheme(QStringLiteral("application-x-executable"))); mDialogButtonBox->addButton(mSendToButton, QDialogButtonBox::ActionRole); mClipboardButton->setDefaultAction(KStandardAction::copy(this, SLOT(sendToClipboard()), this)); mClipboardButton->setText(i18n("Copy To Clipboard")); mClipboardButton->setToolTip(i18n("Copy the current screenshot image to the clipboard.")); mClipboardButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); mClipboardButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mDialogButtonBox->addButton(mClipboardButton, QDialogButtonBox::ActionRole); mSaveButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mSaveButton->setMenu(mSaveMenu); mSaveButton->setPopupMode(QToolButton::MenuButtonPopup); buildSaveMenu(); mDialogButtonBox->addButton(mSaveButton, QDialogButtonBox::ActionRole); QShortcut *shortcut = new QShortcut(QKeySequence(Qt::Key_Escape), mDialogButtonBox->button(QDialogButtonBox::Discard)); connect(shortcut, &QShortcut::activated, qApp, &QApplication::quit); connect(mDialogButtonBox->button(QDialogButtonBox::Discard), &QPushButton::clicked, qApp, &QApplication::quit); // the help menu KHelpMenu *helpMenu = new KHelpMenu(this, KAboutData::applicationData(), true); mDialogButtonBox->button(QDialogButtonBox::Help)->setMenu(helpMenu->menu()); // layouts mDivider->setFrameShape(QFrame::HLine); mDivider->setLineWidth(2); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(mKSWidget); layout->addWidget(mMessageWidget); layout->addWidget(mDivider); layout->addWidget(mDialogButtonBox); mMessageWidget->hide(); // populate our send-to actions mSendToButton->setMenu(mExportMenu); connect(mExportMenu, &ExportMenu::imageShared, this, &KSMainWindow::showImageSharedFeedback); // disable onClick mode if not available on the platform if (!mOnClickAvailable) { mKSWidget->disableOnClick(); } // done with the init } void KSMainWindow::buildSaveMenu() { // first clear the menu mSaveMenu->clear(); // get our actions in order QAction *actionSave = KStandardAction::save(this, SLOT(save()), this); QAction *actionSaveAs = KStandardAction::saveAs(this, SLOT(saveAs()), this); QAction *actionSaveExit = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save &&& Exit"), this); actionSaveExit->setToolTip(i18n("Save screenshot in your Pictures directory and exit")); actionSaveExit->setShortcut(QKeySequence(QKeySequence::Quit)); connect(actionSaveExit, &QAction::triggered, this, &KSMainWindow::saveAndExit); // static or dynamic SpectacleConfig *cfgManager = SpectacleConfig::instance(); int switchState = cfgManager->useDynamicSaveButton() ? cfgManager->lastUsedSaveMode() : 0; // put the actions in order switch (switchState) { case 1: mSaveButton->setDefaultAction(actionSave); mSaveMenu->addAction(actionSaveExit); mSaveMenu->addAction(actionSaveAs); break; case 2: mSaveButton->setDefaultAction(actionSaveAs); mSaveMenu->addAction(actionSaveExit); mSaveMenu->addAction(actionSave); break; case 0: default: mSaveButton->setDefaultAction(actionSaveExit); mSaveMenu->addAction(actionSave); mSaveMenu->addAction(actionSaveAs); break; } // finish off building the menu mSaveMenu->addAction(KStandardAction::print(this, SLOT(showPrintDialog()), this)); mSaveMenu->addSeparator(); mSaveMenu->addAction(QIcon::fromTheme(QStringLiteral("applications-system")), i18n("Preferences"), this, SLOT(showPreferencesDialog())); } // overrides void KSMainWindow::moveEvent(QMoveEvent *event) { Q_UNUSED(event); KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup guiConfig(config, "GuiConfig"); guiConfig.writeEntry("window-position", pos()); guiConfig.sync(); } // slots void KSMainWindow::captureScreenshot(ImageGrabber::GrabMode mode, int timeout, bool includePointer, bool includeDecorations) { hide(); emit newScreenshotRequest(mode, timeout, includePointer, includeDecorations); } void KSMainWindow::setScreenshotAndShow(const QPixmap &pixmap) { mKSWidget->setScreenshotPixmap(pixmap); mExportMenu->imageUpdated(ExportManager::instance()->pixmapDataUri()); setWindowTitle(i18nc("Unsaved Screenshot", "Unsaved[*]")); setWindowModified(true); KGuiItem::assign(mDialogButtonBox->button(QDialogButtonBox::Discard), KStandardGuiItem::discard()); show(); } void KSMainWindow::showPrintDialog() { QPrinter *printer = new QPrinter(QPrinter::HighResolution); QPrintDialog printDialog(printer, this); if (printDialog.exec() == QDialog::Accepted) { ExportManager::instance()->doPrint(printer); return; } delete printer; } void KSMainWindow::showImageSharedFeedback(bool error, const QString &message) { if (error) { mMessageWidget->setMessageType(KMessageWidget::Error); mMessageWidget->setText(i18n("There was a problem sharing the image: %1", message)); mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); } else { mMessageWidget->setMessageType(KMessageWidget::Positive); mMessageWidget->setText(i18n("You can find the shared image at: %1", message)); mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); } mMessageWidget->animatedShow(); QTimer::singleShot(20000, mMessageWidget, &KMessageWidget::animatedHide); } void KSMainWindow::sendToClipboard() { ExportManager::instance()->doCopyToClipboard(); mMessageWidget->setMessageType(KMessageWidget::Information); mMessageWidget->setText(i18n("The screenshot has been copied to the clipboard.")); mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); mMessageWidget->animatedShow(); QTimer::singleShot(10000, mMessageWidget, &KMessageWidget::animatedHide); } void KSMainWindow::showPreferencesDialog() { SettingsDialog prefDialog(this); prefDialog.exec(); } void KSMainWindow::setScreenshotWindowTitle(QUrl location) { setWindowTitle(location.fileName()); setWindowModified(false); KGuiItem::assign(mDialogButtonBox->button(QDialogButtonBox::Discard), KStandardGuiItem::quit()); } void KSMainWindow::save() { SpectacleConfig::instance()->setLastUsedSaveMode(1); buildSaveMenu(); ExportManager::instance()->doSave(); } void KSMainWindow::saveAs() { SpectacleConfig::instance()->setLastUsedSaveMode(2); buildSaveMenu(); ExportManager::instance()->doSaveAs(this); } void KSMainWindow::saveAndExit() { SpectacleConfig::instance()->setLastUsedSaveMode(0); - ExportManager::instance()->doSave(); - QApplication::quit(); + qApp->setQuitOnLastWindowClosed(false); + ExportManager::instance()->doSave(QUrl(), true); + hide(); } diff --git a/src/Gui/KSWidget.cpp b/src/Gui/KSWidget.cpp index bc9594c..7b66d55 100644 --- a/src/Gui/KSWidget.cpp +++ b/src/Gui/KSWidget.cpp @@ -1,223 +1,224 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * 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 "KSWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include "KSImageWidget.h" #include "SmartSpinBox.h" #include "SpectacleConfig.h" KSWidget::KSWidget(QWidget *parent) : QWidget(parent) { // get a handle to the configuration manager SpectacleConfig *configManager = SpectacleConfig::instance(); // we'll init the widget that holds the image first mImageWidget = new KSImageWidget(this); mImageWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); connect(mImageWidget, &KSImageWidget::dragInitiated, this, &KSWidget::dragInitiated); // the capture mode options first mCaptureModeLabel = new QLabel(this); mCaptureModeLabel->setText(i18n("Capture Mode")); mCaptureArea = new QComboBox(this); mCaptureArea->insertItem(1, i18n("Full Screen (All Monitors)"), ImageGrabber::FullScreen); mCaptureArea->insertItem(2, i18n("Current Screen"), ImageGrabber::CurrentScreen); mCaptureArea->insertItem(3, i18n("Active Window"), ImageGrabber::ActiveWindow); mCaptureArea->insertItem(4, i18n("Window Under Cursor"), ImageGrabber::WindowUnderCursor); mCaptureArea->insertItem(5, i18n("Rectangular Region"), ImageGrabber::RectangularRegion); mCaptureArea->setMinimumWidth(240); connect(mCaptureArea, static_cast(&QComboBox::currentIndexChanged), this, &KSWidget::captureModeChanged); mDelayMsec = new SmartSpinBox(this); mDelayMsec->setDecimals(1); mDelayMsec->setSingleStep(1.0); mDelayMsec->setMinimum(0.0); mDelayMsec->setMaximum(999.9); - mDelayMsec->setSuffix(i18n(" seconds")); + mDelayMsec->setSpecialValueText(i18n("No Delay")); mDelayMsec->setMinimumWidth(160); - connect(mDelayMsec, static_cast(&SmartSpinBox::valueChanged), configManager, &SpectacleConfig::setCaptureDelay); + connect(mDelayMsec, static_cast(&SmartSpinBox::valueChanged), + configManager, &SpectacleConfig::setCaptureDelay); mCaptureOnClick = new QCheckBox(this); mCaptureOnClick->setText(i18n("On Click")); mCaptureOnClick->setToolTip(i18n("Wait for a mouse click before capturing the screenshot image")); connect(mCaptureOnClick, &QCheckBox::stateChanged, this, &KSWidget::onClickStateChanged); connect(mCaptureOnClick, &QCheckBox::stateChanged, configManager, &SpectacleConfig::setOnClickChecked); mDelayLayout = new QHBoxLayout; mDelayLayout->addWidget(mDelayMsec); mDelayLayout->addWidget(mCaptureOnClick); mCaptureModeForm = new QFormLayout; mCaptureModeForm->addRow(i18n("Area:"), mCaptureArea); mCaptureModeForm->addRow(i18n("Delay:"), mDelayLayout); mCaptureModeForm->setContentsMargins(24, 0, 0, 0); // the content options (mouse pointer, window decorations) mContentOptionsLabel = new QLabel(this); mContentOptionsLabel->setText(i18n("Content Options")); mMousePointer = new QCheckBox(this); mMousePointer->setText(i18n("Include mouse pointer")); mMousePointer->setToolTip(i18n("Show the mouse cursor in the screeenshot image")); connect(mMousePointer, &QCheckBox::stateChanged, configManager, &SpectacleConfig::setIncludePointerChecked); mWindowDecorations = new QCheckBox(this); mWindowDecorations->setText(i18n("Include window titlebar and borders")); mWindowDecorations->setToolTip(i18n("Show the window title bar, the minimize/maximize/close buttons, and the window border")); mWindowDecorations->setEnabled(false); connect(mWindowDecorations, &QCheckBox::stateChanged, configManager, &SpectacleConfig::setIncludeDecorationsChecked); mCaptureTransientOnly = new QCheckBox(this); mCaptureTransientOnly->setText(i18n("Capture the current pop-up only")); mCaptureTransientOnly->setToolTip(i18n("Capture only the current pop-up window (like a menu, tooltip etc). " "If this is not enabled, the pop-up is captured along with the parent window")); mCaptureTransientOnly->setEnabled(false); connect(mCaptureTransientOnly, &QCheckBox::stateChanged, configManager, &SpectacleConfig::setCaptureTransientWindowOnlyChecked); mContentOptionsForm = new QVBoxLayout; mContentOptionsForm->addWidget(mMousePointer); mContentOptionsForm->addWidget(mWindowDecorations); mContentOptionsForm->addWidget(mCaptureTransientOnly); mContentOptionsForm->setSpacing(16); mContentOptionsForm->setContentsMargins(24, 0, 0, 0); // the take a new screenshot button mTakeScreenshotButton = new QPushButton(this); mTakeScreenshotButton->setText(i18n("Take a New Screenshot")); mTakeScreenshotButton->setIcon(QIcon::fromTheme(QStringLiteral("spectacle"))); mTakeScreenshotButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); mTakeScreenshotButton->setFocus(); connect(mTakeScreenshotButton, &QPushButton::clicked, this, &KSWidget::newScreenshotClicked); QShortcut *shortcut = new QShortcut(QKeySequence(QKeySequence::New), mTakeScreenshotButton); auto clickFunc = [&]() { mTakeScreenshotButton->animateClick(100); QTimer::singleShot(100, mTakeScreenshotButton, &QPushButton::click); }; connect(shortcut, &QShortcut::activated, clickFunc); // finally, finish up the layouts mRightLayout = new QVBoxLayout; mRightLayout->addStretch(1); mRightLayout->addWidget(mCaptureModeLabel); mRightLayout->addSpacing(10); mRightLayout->addLayout(mCaptureModeForm); mRightLayout->addStretch(1); mRightLayout->addWidget(mContentOptionsLabel); mRightLayout->addSpacing(10); mRightLayout->addLayout(mContentOptionsForm); mRightLayout->addStretch(10); mRightLayout->addWidget(mTakeScreenshotButton, 1, Qt::AlignHCenter); mRightLayout->setContentsMargins(20, 0, 0, 10); mMainLayout = new QGridLayout(this); mMainLayout->addWidget(mImageWidget, 0, 0, 1, 1); mMainLayout->addLayout(mRightLayout, 0, 1, 1, 1); mMainLayout->setColumnMinimumWidth(0, 400); mMainLayout->setColumnMinimumWidth(1, 400); // and read in the saved checkbox states and capture mode indices mMousePointer->setChecked (configManager->includePointerChecked()); mWindowDecorations->setChecked (configManager->includeDecorationsChecked()); mCaptureOnClick->setChecked (configManager->onClickChecked()); mCaptureTransientOnly->setChecked (configManager->captureTransientWindowOnlyChecked()); mCaptureArea->setCurrentIndex (configManager->captureMode()); mDelayMsec->setValue (configManager->captureDelay()); // done } // public slots void KSWidget::setScreenshotPixmap(const QPixmap &pixmap) { mImageWidget->setScreenshot(pixmap); } void KSWidget::disableOnClick() { mCaptureOnClick->setEnabled(false); mDelayMsec->setEnabled(true); } // private slots void KSWidget::newScreenshotClicked() { int delay = mCaptureOnClick->isChecked() ? -1 : (mDelayMsec->value() * 1000); ImageGrabber::GrabMode mode = static_cast(mCaptureArea->currentData().toInt()); if (mode == ImageGrabber::WindowUnderCursor && !(mCaptureTransientOnly->isChecked())) { mode = ImageGrabber::TransientWithParent; } emit newScreenshotRequest(mode, delay, mMousePointer->isChecked(), mWindowDecorations->isChecked()); } void KSWidget::onClickStateChanged(int state) { if (state == Qt::Checked) { mDelayMsec->setEnabled(false); } else if (state == Qt::Unchecked) { mDelayMsec->setEnabled(true); } } void KSWidget::captureModeChanged(int index) { SpectacleConfig::instance()->setCaptureMode(index); ImageGrabber::GrabMode mode = static_cast(mCaptureArea->itemData(index).toInt()); switch (mode) { case ImageGrabber::WindowUnderCursor: mWindowDecorations->setEnabled(true); mCaptureTransientOnly->setEnabled(true); break; case ImageGrabber::ActiveWindow: mWindowDecorations->setEnabled(true); mCaptureTransientOnly->setEnabled(false); break; default: mWindowDecorations->setEnabled(false); mCaptureTransientOnly->setEnabled(false); } } diff --git a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp index ab892dc..1ffddd9 100644 --- a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp +++ b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp @@ -1,147 +1,174 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * 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 "GeneralOptionsPage.h" #include #include #include #include #include #include "SpectacleConfig.h" GeneralOptionsPage::GeneralOptionsPage(QWidget *parent) : SettingsPage(parent) { // preamble and stuff QVBoxLayout *mainLayout = new QVBoxLayout(this); // dynamic save button QGroupBox *dsGroup = new QGroupBox(i18n("Dynamic Save Button")); QVBoxLayout *dsLayout = new QVBoxLayout; dsGroup->setLayout(dsLayout); dsGroup->setStyleSheet(QStringLiteral("QGroupBox { font-weight: bold; }")); QLabel *dsHelpText = new QLabel; dsHelpText->setWordWrap(true); dsHelpText->setText(i18n("The default behavior of the save button is to Save & Exit. Enable this " "option to change the save button to either Save, Save As or " "Save & Exit, according to the save action you perform, every time you " "save an image.")); dsLayout->addWidget(dsHelpText); mUseLastSaveAction = new QCheckBox; mUseLastSaveAction->setText(i18n("Set save button action to last used save method")); connect(mUseLastSaveAction, &QCheckBox::toggled, this, &GeneralOptionsPage::markDirty); QVBoxLayout *dsCLayout = new QVBoxLayout; dsCLayout->setContentsMargins(15, 10, 0, 10); dsCLayout->addWidget(mUseLastSaveAction); dsLayout->addLayout(dsCLayout); mainLayout->addWidget(dsGroup, 1); + // copy save path to clipboard + + QGroupBox *cpGroup = new QGroupBox(i18n("Copy Save Location to Clipboard")); + QVBoxLayout *cpLayout = new QVBoxLayout; + cpGroup->setLayout(cpLayout); + cpGroup->setStyleSheet(QStringLiteral("QGroupBox { font-weight: bold; }")); + + QLabel *cpHelpText = new QLabel; + cpHelpText->setWordWrap(true); + cpHelpText->setText(i18n("When a screenshot is saved, copy the location at which the file was saved " + "to the clipboard. You can then paste it anywhere that accepts text input. " + "Note that you must be running a clipboard manager in order to keep the path " + "in the clipboard after Spectacle quits.")); + cpLayout->addWidget(cpHelpText); + + mCopyPathToClipboard = new QCheckBox; + mCopyPathToClipboard->setText(i18n("Copy save location to the clipboard")); + connect(mCopyPathToClipboard, &QCheckBox::toggled, this, &GeneralOptionsPage::markDirty); + + QVBoxLayout *cpCLayout = new QVBoxLayout; + cpCLayout->setContentsMargins(15, 10, 0, 10); + cpCLayout->addWidget(mCopyPathToClipboard); + cpLayout->addLayout(cpCLayout); + mainLayout->addWidget(cpGroup, 1); + // use a light background for the rectangular region grabber QGroupBox *lbGroup = new QGroupBox(i18n("Light Background For Rectangular Region")); QVBoxLayout *lbLayout = new QVBoxLayout; lbGroup->setLayout(lbLayout); lbGroup->setStyleSheet(QStringLiteral("QGroupBox { font-weight: bold; }")); QLabel *lbHelpText = new QLabel; lbHelpText->setWordWrap(true); lbHelpText->setText(i18n("Use a light background color to mask the cropped-out area in the rectangular " "region selector. This may make dark cursors easier to see.")); lbLayout->addWidget(lbHelpText); mUseLightBackground = new QCheckBox; mUseLightBackground->setText(i18n("Use light background color")); connect(mUseLightBackground, &QCheckBox::toggled, this, &GeneralOptionsPage::markDirty); QVBoxLayout *lbCLayout = new QVBoxLayout; lbCLayout->setContentsMargins(15, 10, 0, 10); lbCLayout->addWidget(mUseLightBackground); lbLayout->addLayout(lbCLayout); mainLayout->addWidget(lbGroup, 1); // remember rectangular region QGroupBox *rrGroup = new QGroupBox(i18n("Remember Rectangular Region")); QVBoxLayout *rrLayout = new QVBoxLayout; rrGroup->setLayout(rrLayout); rrGroup->setStyleSheet(QStringLiteral("QGroupBox { font-weight: bold; }")); QLabel *rrHelpText = new QLabel; rrHelpText->setWordWrap(true); rrHelpText->setText(i18n("By default, Spectacle does not show an initial selection when you take a " "screenshot of a rectangular region. Enable this option to remember the last " "selected region of the screen, and set it as the initial selection when you " "use the rectangular region selector the next time.")); rrLayout->addWidget(rrHelpText); mRememberRect = new QCheckBox; mRememberRect->setText(i18n("Remember rectangular region")); connect(mRememberRect, &QCheckBox::toggled, this, &GeneralOptionsPage::markDirty); QVBoxLayout *rrCLayout = new QVBoxLayout; rrCLayout->setContentsMargins(15, 10, 0, 10); rrCLayout->addWidget(mRememberRect); rrLayout->addLayout(rrCLayout); mainLayout->addWidget(rrGroup, 1); // read in the data resetChanges(); // finish up with the main layout mainLayout->addStretch(4); setLayout(mainLayout); } void GeneralOptionsPage::markDirty(bool checked) { Q_UNUSED(checked); mChangesMade = true; } void GeneralOptionsPage::saveChanges() { SpectacleConfig *cfgManager = SpectacleConfig::instance(); cfgManager->setUseDynamicSaveButton(mUseLastSaveAction->checkState() == Qt::Checked); cfgManager->setUseLightRegionMaskColour(mUseLightBackground->checkState() == Qt::Checked); cfgManager->setRememberLastRectangularRegion(mRememberRect->checkState() == Qt::Checked); + cfgManager->setCopySaveLocationToClipboard(mCopyPathToClipboard->checkState() == Qt::Checked); mChangesMade = false; } void GeneralOptionsPage::resetChanges() { SpectacleConfig *cfgManager = SpectacleConfig::instance(); mUseLastSaveAction->setChecked(cfgManager->useDynamicSaveButton()); mUseLightBackground->setChecked(cfgManager->useLightRegionMaskColour()); mRememberRect->setChecked(cfgManager->rememberLastRectangularRegion()); + mCopyPathToClipboard->setChecked(cfgManager->copySaveLocationToClipboard()); mChangesMade = false; } diff --git a/src/Gui/SettingsDialog/GeneralOptionsPage.h b/src/Gui/SettingsDialog/GeneralOptionsPage.h index 9843cb7..343ce03 100644 --- a/src/Gui/SettingsDialog/GeneralOptionsPage.h +++ b/src/Gui/SettingsDialog/GeneralOptionsPage.h @@ -1,51 +1,52 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * 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 GENERALOPTIONSPAGE_H #define GENERALOPTIONSPAGE_H #include "SettingsPage.h" class QCheckBox; class GeneralOptionsPage : public SettingsPage { Q_OBJECT public: explicit GeneralOptionsPage(QWidget *parent = 0); public slots: void saveChanges() Q_DECL_OVERRIDE; void resetChanges() Q_DECL_OVERRIDE; private slots: void markDirty(bool checked); private: QCheckBox *mUseLastSaveAction; QCheckBox *mRememberRect; QCheckBox *mUseLightBackground; + QCheckBox *mCopyPathToClipboard; }; #endif // GENERALOPTIONSPAGE_H diff --git a/src/Gui/SmartSpinBox.cpp b/src/Gui/SmartSpinBox.cpp index eb801aa..4938f84 100644 --- a/src/Gui/SmartSpinBox.cpp +++ b/src/Gui/SmartSpinBox.cpp @@ -1,33 +1,47 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * 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 "SmartSpinBox.h" SmartSpinBox::SmartSpinBox(QWidget *parent) : QDoubleSpinBox(parent) -{} +{ + connect(this, static_cast(&SmartSpinBox::valueChanged), + this, &SmartSpinBox::suffixChangeHandler); +} QString SmartSpinBox::textFromValue(double val) const { if ((qFloor(val) == val) && (qCeil(val) == val)) { return QWidget::locale().toString(qint64(val)); } return QWidget::locale().toString(val, 'f', decimals()); } + +void SmartSpinBox::suffixChangeHandler(double val) +{ + if (val <= 1.0) { + setSuffix(i18n(" second")); + } else { + setSuffix(i18n(" seconds")); + } +} diff --git a/src/Gui/SmartSpinBox.h b/src/Gui/SmartSpinBox.h index b75e1cf..c887b0a 100644 --- a/src/Gui/SmartSpinBox.h +++ b/src/Gui/SmartSpinBox.h @@ -1,35 +1,39 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * 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 SMARTSPINBOX_H #define SMARTSPINBOX_H #include class SmartSpinBox : public QDoubleSpinBox { Q_OBJECT public: explicit SmartSpinBox(QWidget *parent = 0); QString textFromValue(double val) const Q_DECL_OVERRIDE; + + private slots: + + void suffixChangeHandler(double val); }; #endif // SMARTSPINBOX_H diff --git a/src/KipiInterface/KSGKipiImageCollectionSelector.cpp b/src/KipiInterface/KSGKipiImageCollectionSelector.cpp index 8a5786c..c6a9676 100644 --- a/src/KipiInterface/KSGKipiImageCollectionSelector.cpp +++ b/src/KipiInterface/KSGKipiImageCollectionSelector.cpp @@ -1,66 +1,66 @@ /* * Copyright (C) 2015 Boudhayan Gupta * 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. * */ #include "KSGKipiImageCollectionSelector.h" KSGKipiImageCollectionSelector::KSGKipiImageCollectionSelector(KIPI::Interface *interface, QWidget *parent) : KIPI::ImageCollectionSelector(parent), mInterface(interface), mListWidget(new QListWidget) { - for(auto collection: interface->allAlbums()) { + Q_FOREACH (const auto &collection, interface->allAlbums()) { QListWidgetItem *item = new QListWidgetItem(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(mListWidget, &QListWidget::currentRowChanged, this, &KIPI::ImageCollectionSelector::selectionChanged); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(mListWidget); layout->setMargin(0); } KSGKipiImageCollectionSelector::~KSGKipiImageCollectionSelector() {} QList KSGKipiImageCollectionSelector::selectedImageCollections() const { QListWidgetItem *item = mListWidget->currentItem(); QList selectedList; if (item) { QString name = item->data(Qt::UserRole).toString(); - for(auto collection: mInterface->allAlbums()) { + Q_FOREACH (const auto &collection, mInterface->allAlbums()) { if (collection.name() == name) { selectedList.append(collection); break; } } } return selectedList; } diff --git a/src/Main.cpp b/src/Main.cpp index bdfcbe7..22d29f0 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -1,162 +1,165 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * 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 #include #include #include #include "SpectacleCore.h" #include "SpectacleDBusAdapter.h" +#include "ExportManager.h" #include "Config.h" int main(int argc, char **argv) { // set up the application QApplication app(argc, argv); app.setOrganizationDomain("kde.org"); app.setApplicationName("spectacle"); app.setWindowIcon(QIcon::fromTheme("spectacle")); app.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); // set up the about data KLocalizedString::setApplicationDomain("spectacle"); KAboutData aboutData("spectacle", i18n("Spectacle"), QStringLiteral(SPECTACLE_VERSION) + QStringLiteral(" - ") + QStringLiteral(SPECTACLE_CODENAME), i18n("KDE Screenshot Utility"), KAboutLicense::GPL_V2, i18n("(C) 2015 Boudhayan Gupta")); aboutData.addAuthor("Boudhayan Gupta", QString(), "bgupta@kde.org"); aboutData.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails")); KAboutData::setApplicationData(aboutData); // set up the command line options parser QCommandLineParser parser; parser.addVersionOption(); parser.addHelpOption(); aboutData.setupCommandLine(&parser); parser.addOptions({ {{"f", "fullscreen"}, i18n("Capture the entire desktop (default)")}, {{"m", "current"}, i18n("Capture the current monitor")}, {{"a", "activewindow"}, i18n("Capture the active window")}, {{"u", "windowundercursor"}, i18n("Capture the window currently under the cursor, including parents of pop-up menus")}, {{"t", "transientonly"}, i18n("Capture the window currently under the cursor, excluding parents of pop-up menus")}, {{"r", "region"}, i18n("Capture a rectangular region of the screen")}, {{"g", "gui"}, i18n("Start in GUI mode (default)")}, {{"b", "background"}, i18n("Take a screenshot and exit without showing the GUI")}, {{"s", "dbus"}, i18n("Start in DBus-Activation mode")}, {{"n", "nonotify"}, i18n("In background mode, do not pop up a notification when the screenshot is taken")}, {{"o", "output"}, i18n("In background mode, save image to specified file"), "fileName"}, {{"d", "delay"}, i18n("In background mode, delay before taking the shot (in milliseconds)"), "delayMsec"}, {{"w", "onclick"}, i18n("Wait for a click before taking screenshot. Invalidates delay")} }); parser.process(app); aboutData.processCommandLine(&parser); // extract the capture mode ImageGrabber::GrabMode grabMode = ImageGrabber::FullScreen; if (parser.isSet("current")) { grabMode = ImageGrabber::CurrentScreen; } else if (parser.isSet("activewindow")) { grabMode = ImageGrabber::ActiveWindow; } else if (parser.isSet("region")) { grabMode = ImageGrabber::RectangularRegion; } else if (parser.isSet("windowundercursor")) { grabMode = ImageGrabber::TransientWithParent; } else if (parser.isSet("transientonly")) { grabMode = ImageGrabber::WindowUnderCursor; } // are we running in background or dbus mode? SpectacleCore::StartMode startMode = SpectacleCore::GuiMode; bool notify = true; qint64 delayMsec = 0; QString fileName = QString(); if (parser.isSet("background")) { startMode = SpectacleCore::BackgroundMode; } else if (parser.isSet("dbus")) { startMode = SpectacleCore::DBusMode; } switch (startMode) { case SpectacleCore::BackgroundMode: if (parser.isSet("nonotify")) { notify = false; } if (parser.isSet("output")) { fileName = parser.value("output"); } if (parser.isSet("delay")) { bool ok = false; qint64 delayValue = parser.value("delay").toLongLong(&ok); if (ok) { delayMsec = delayValue; } } if (parser.isSet("onclick")) { delayMsec = -1; } case SpectacleCore::DBusMode: app.setQuitOnLastWindowClosed(false); case SpectacleCore::GuiMode: break; } // release the kraken SpectacleCore core(startMode, grabMode, fileName, delayMsec, notify); QObject::connect(&core, &SpectacleCore::allDone, qApp, &QApplication::quit); // create the dbus connections new KDBusService(KDBusService::Multiple, &core); SpectacleDBusAdapter *dbusAdapter = new SpectacleDBusAdapter(&core); - QObject::connect(&core, &SpectacleCore::imageSaved, dbusAdapter, &SpectacleDBusAdapter::ScreenshotTaken); QObject::connect(&core, &SpectacleCore::grabFailed, dbusAdapter, &SpectacleDBusAdapter::ScreenshotFailed); + QObject::connect(ExportManager::instance(), &ExportManager::imageSaved, [&](const QUrl savedAt) { + emit dbusAdapter->ScreenshotTaken(savedAt.toLocalFile()); + }); QDBusConnection::sessionBus().registerObject("/", &core); QDBusConnection::sessionBus().registerService("org.kde.Spectacle"); // fire it up return app.exec(); } diff --git a/src/PlatformBackends/X11ImageGrabber.cpp b/src/PlatformBackends/X11ImageGrabber.cpp index 61fce53..ecc3e3a 100644 --- a/src/PlatformBackends/X11ImageGrabber.cpp +++ b/src/PlatformBackends/X11ImageGrabber.cpp @@ -1,710 +1,705 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * Contains code from kxutils.cpp, part of KWindowSystem. Copyright * notices reproduced below: * * Copyright (C) 2008 Lubos Lunak (l.lunak@kde.org) * Copyright (C) 2013 Martin Gräßlin * * 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 "X11ImageGrabber.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include X11ImageGrabber::X11ImageGrabber(QObject *parent) : ImageGrabber(parent), mScreenConfigOperation(nullptr) { mNativeEventFilter = new OnClickEventFilter(this); } X11ImageGrabber::~X11ImageGrabber() { delete mNativeEventFilter; } // for onClick grab OnClickEventFilter::OnClickEventFilter(X11ImageGrabber *grabber) : QAbstractNativeEventFilter(), mImageGrabber(grabber) {} bool OnClickEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { Q_UNUSED(result); if (eventType == "xcb_generic_event_t") { xcb_generic_event_t *ev = static_cast(message); switch (ev->response_type & ~0x80) { case XCB_BUTTON_RELEASE: // uninstall the eventfilter and release the mouse qApp->removeNativeEventFilter(this); xcb_ungrab_pointer(QX11Info::connection(), XCB_TIME_CURRENT_TIME); // decide whether to grab or abort. regrab the mouse // on mouse-wheel events { xcb_button_release_event_t *ev2 = static_cast(message); qDebug() << ev2->detail; if (ev2->detail == 1) { QMetaObject::invokeMethod(mImageGrabber, "doImageGrab", Qt::QueuedConnection); } else if (ev2->detail < 4) { emit mImageGrabber->imageGrabFailed(); } else { QMetaObject::invokeMethod(mImageGrabber, "doOnClickGrab", Qt::QueuedConnection); } } return true; default: return false; } } return false; } bool X11ImageGrabber::onClickGrabSupported() const { return true; } void X11ImageGrabber::doOnClickGrab() { // get the cursor image xcb_cursor_t xcbCursor = XCB_CURSOR_NONE; xcb_cursor_context_t *xcbCursorCtx; xcb_screen_t *xcbAppScreen = xcb_aux_get_screen(QX11Info::connection(), QX11Info::appScreen()); if (xcb_cursor_context_new(QX11Info::connection(), xcbAppScreen, &xcbCursorCtx) >= 0) { QVector cursorNames = { QByteArrayLiteral("cross"), QByteArrayLiteral("crosshair"), QByteArrayLiteral("diamond-cross"), QByteArrayLiteral("cross-reverse") }; - for (auto cursorName: cursorNames) { + Q_FOREACH (const QByteArray &cursorName, cursorNames) { xcb_cursor_t cursor = xcb_cursor_load_cursor(xcbCursorCtx, cursorName.constData()); if (cursor != XCB_CURSOR_NONE) { xcbCursor = cursor; break; } } } // grab the cursor xcb_grab_pointer_cookie_t grabPointerCookie = xcb_grab_pointer_unchecked( QX11Info::connection(), // xcb connection 0, // deliver events to owner? nope QX11Info::appRootWindow(), // window to grab pointer for (root) XCB_EVENT_MASK_BUTTON_RELEASE, // which events do I want XCB_GRAB_MODE_SYNC, // pointer grab mode XCB_GRAB_MODE_ASYNC, // keyboard grab mode (why is this even here) XCB_NONE, // confine pointer to which window (none) xcbCursor, // cursor to change to for the duration of grab XCB_TIME_CURRENT_TIME // do this right now ); CScopedPointer grabPointerReply(xcb_grab_pointer_reply(QX11Info::connection(), grabPointerCookie, NULL)); // if the grab failed, take the screenshot right away if (grabPointerReply->status != XCB_GRAB_STATUS_SUCCESS) { return doImageGrab(); } // fix things if our pointer grab causes a lockup xcb_allow_events(QX11Info::connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); // and install our event filter qApp->installNativeEventFilter(mNativeEventFilter); // done. clean stuff up xcb_cursor_context_free(xcbCursorCtx); xcb_free_cursor(QX11Info::connection(), xcbCursor); } // image conversion routine QPixmap X11ImageGrabber::convertFromNative(xcb_image_t *xcbImage) { QImage::Format format = QImage::Format_Invalid; switch (xcbImage->depth) { case 1: format = QImage::Format_MonoLSB; break; case 16: format = QImage::Format_RGB16; break; case 24: format = QImage::Format_RGB32; break; - case 30: { - // Qt doesn't have a matching image format. We need to convert manually - quint32 *pixels = reinterpret_cast(xcbImage->data); - for (uint i = 0; i < (xcbImage->size / 4); i++) { - int r = (pixels[i] >> 22) & 0xff; - int g = (pixels[i] >> 12) & 0xff; - int b = (pixels[i] >> 2) & 0xff; - - pixels[i] = qRgba(r, g, b, 0xff); - } - // fall through, Qt format is still Format_ARGB32_Premultiplied - } + case 30: + format = QImage::Format_BGR30; + break; case 32: format = QImage::Format_ARGB32_Premultiplied; break; default: return QPixmap(); // we don't know } QImage image(xcbImage->data, xcbImage->width, xcbImage->height, format); if (image.isNull()) { return QPixmap(); } // work around an abort in QImage::color if (image.format() == QImage::Format_MonoLSB) { image.setColorCount(2); image.setColor(0, QColor(Qt::white).rgb()); image.setColor(1, QColor(Qt::black).rgb()); } // done return QPixmap::fromImage(image); } // utility functions QPixmap X11ImageGrabber::blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) { // first we get the cursor position, compute the co-ordinates of the region // of the screen we're grabbing, and see if the cursor is actually visible in // the region - QPoint cursorPos = QCursor::pos(); - QRect screenRect(x, y, width, height); + const qreal dpr = pixmap.devicePixelRatio(); + + // cursor position operates on application's device pixel ratio, not the pixmap! + QPoint cursorPos = QCursor::pos() / dpr * qApp->devicePixelRatio(); + QRect screenRect(x / dpr, y / dpr, width / dpr, height / dpr); - if (!(screenRect.contains(cursorPos))) { + if (!screenRect.contains(cursorPos)) { return pixmap; } // now we can get the image and start processing xcb_connection_t *xcbConn = QX11Info::connection(); xcb_xfixes_get_cursor_image_cookie_t cursorCookie = xcb_xfixes_get_cursor_image_unchecked(xcbConn); CScopedPointer cursorReply(xcb_xfixes_get_cursor_image_reply(xcbConn, cursorCookie, NULL)); if (cursorReply.isNull()) { return pixmap; } quint32 *pixelData = xcb_xfixes_get_cursor_image_cursor_image(cursorReply.data()); if (!pixelData) { return pixmap; } // process the image into a QImage QImage cursorImage = QImage((quint8 *)pixelData, cursorReply->width, cursorReply->height, QImage::Format_ARGB32_Premultiplied); + cursorImage.setDevicePixelRatio(dpr); // a small fix for the cursor position for fancier cursors - cursorPos -= QPoint(cursorReply->xhot, cursorReply->yhot); + cursorPos -= QPoint(cursorReply->xhot, cursorReply->yhot) / dpr; // now we translate the cursor point to our screen rectangle - cursorPos -= QPoint(x, y); + cursorPos -= QPoint(x, y) / dpr; // and do the painting QPixmap blendedPixmap = pixmap; QPainter painter(&blendedPixmap); painter.drawImage(cursorPos, cursorImage); // and done return blendedPixmap; } QPixmap X11ImageGrabber::getWindowPixmap(xcb_window_t window, bool blendPointer) { xcb_connection_t *xcbConn = QX11Info::connection(); // first get geometry information for our drawable xcb_get_geometry_cookie_t geomCookie = xcb_get_geometry_unchecked(xcbConn, window); CScopedPointer geomReply(xcb_get_geometry_reply(xcbConn, geomCookie, NULL)); // then proceed to get an image CScopedPointer xcbImage( xcb_image_get( xcbConn, window, geomReply->x, geomReply->y, geomReply->width, geomReply->height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP ) ); // if the image is null, this means we need to get the root image window // and run a crop if (xcbImage.isNull()) { return getWindowPixmap(QX11Info::appRootWindow(), blendPointer) .copy(geomReply->x, geomReply->y, geomReply->width, geomReply->height); } // now process the image QPixmap nativePixmap = convertFromNative(xcbImage.data()); if (!(blendPointer)) { return nativePixmap; } // now we blend in a pointer image xcb_get_geometry_cookie_t geomRootCookie = xcb_get_geometry_unchecked(xcbConn, geomReply->root); CScopedPointer geomRootReply(xcb_get_geometry_reply(xcbConn, geomRootCookie, NULL)); xcb_translate_coordinates_cookie_t translateCookie = xcb_translate_coordinates_unchecked( xcbConn, window, geomReply->root, geomRootReply->x, geomRootReply->y); CScopedPointer translateReply( xcb_translate_coordinates_reply(xcbConn, translateCookie, NULL)); return blendCursorImage(nativePixmap, translateReply->dst_x,translateReply->dst_y, geomReply->width, geomReply->height); } bool X11ImageGrabber::isKWinAvailable() { if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.KWin"))) { QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Effects"), QStringLiteral("org.kde.kwin.Effects")); QDBusReply reply = interface.call(QStringLiteral("isEffectLoaded"), "screenshot"); return reply.value(); } return false; } void X11ImageGrabber::KWinDBusScreenshotHelper(quint64 window) { mPixmap = getWindowPixmap((xcb_window_t)window, false); emit pixmapChanged(mPixmap); } void X11ImageGrabber::KScreenCurrentMonitorScreenshotHelper(KScreen::ConfigOperation *op) { KScreen::ConfigPtr config = qobject_cast(op)->config(); if (!config) { return grabFullScreen(); } if (!config->screen()) { return grabFullScreen(); } // we'll store the cursor position first QPoint cursorPosition = QCursor::pos(); // next, we'll get all our outputs and figure out which one has the cursor const KScreen::OutputList outputs = config->outputs(); for (auto output: outputs) { if (!(output->isConnected())) { continue; } if (!(output->currentMode())) { continue; } QPoint screenPosition = output->pos(); QSize screenSize = output->currentMode()->size(); QRect screenRect = QRect(screenPosition, screenSize); if (!(screenRect.contains(cursorPosition))) { continue; } // bingo, we've found an output that contains the cursor. Now // to take a shot mPixmap = getWindowPixmap(QX11Info::appRootWindow(), mCapturePointer); mPixmap = mPixmap.copy(screenPosition.x(), screenPosition.y(), screenSize.width(), screenSize.height()); emit pixmapChanged(mPixmap); mScreenConfigOperation->disconnect(); mScreenConfigOperation->deleteLater(); mScreenConfigOperation = nullptr; return; } mScreenConfigOperation->disconnect(); mScreenConfigOperation->deleteLater(); mScreenConfigOperation = nullptr; return grabFullScreen(); } void X11ImageGrabber::rectangleSelectionCancelled() { QObject *sender = QObject::sender(); sender->disconnect(); sender->deleteLater(); emit imageGrabFailed(); } void X11ImageGrabber::rectangleSelectionConfirmed(const QPixmap &pixmap, const QRect ®ion) { QObject *sender = QObject::sender(); sender->disconnect(); sender->deleteLater(); if (mCapturePointer) { mPixmap = blendCursorImage(pixmap, region.x(), region.y(), region.width(), region.height()); } else { mPixmap = pixmap; } emit pixmapChanged(mPixmap); } // grabber methods void X11ImageGrabber::grabFullScreen() { mPixmap = getWindowPixmap(QX11Info::appRootWindow(), mCapturePointer); emit pixmapChanged(mPixmap); } void X11ImageGrabber::grabTransientWithParent() { xcb_window_t curWin = getRealWindowUnderCursor(); // do we have a top-level or a transient window? KWindowInfo info(curWin, NET::WMName, NET::WM2TransientFor | NET::WM2WindowClass); if (!(info.valid(true) && (info.transientFor() != XCB_WINDOW_NONE)) || info.windowClassClass().isEmpty() || info.windowClassName().isEmpty()) { return grabWindowUnderCursor(); } // grab the image early mPixmap = getWindowPixmap(QX11Info::appRootWindow(), false); // now that we know we have a transient window, let's // see if the parent has any other transient windows who's // transient for the same app QRegion clipRegion; QStack childrenStack = findAllChildren(findParent(curWin)); while (!(childrenStack.isEmpty())) { xcb_window_t winId = childrenStack.pop(); KWindowInfo tempInfo(winId, 0, NET::WM2TransientFor); if (info.transientFor() == tempInfo.transientFor()) { clipRegion += getApplicationWindowGeometry(winId); } } // now we have a list of all the transient windows for the // parent, time to find the parent QList winList = KWindowSystem::stackingOrder(); for (int i = winList.size() - 1; i >= 0; i--) { KWindowInfo tempInfo(winList[i], NET::WMGeometry | NET::WMFrameExtents, NET::WM2WindowClass); QString winClass(tempInfo.windowClassClass()); QString winName(tempInfo.windowClassName()); if (winClass.contains(info.name(), Qt::CaseInsensitive) || winName.contains(info.name(), Qt::CaseInsensitive)) { if (mCaptureDecorations) { clipRegion += tempInfo.frameGeometry(); } else { clipRegion += tempInfo.geometry(); } break; } } // we can probably go ahead and generate the image now QImage tempImage(mPixmap.size(), QImage::Format_ARGB32); tempImage.fill(Qt::transparent); QPainter tempPainter(&tempImage); tempPainter.setClipRegion(clipRegion); tempPainter.drawPixmap(0, 0, mPixmap); tempPainter.end(); mPixmap = QPixmap::fromImage(tempImage).copy(clipRegion.boundingRect()); // why stop here, when we can render a 20px drop shadow all around it QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect; effect->setOffset(0); effect->setBlurRadius(20); QGraphicsPixmapItem *item = new QGraphicsPixmapItem; item->setPixmap(mPixmap); item->setGraphicsEffect(effect); QImage shadowImage(mPixmap.size() + QSize(40, 40), QImage::Format_ARGB32); shadowImage.fill(Qt::transparent); QPainter shadowPainter(&shadowImage); QGraphicsScene scene; scene.addItem(item); scene.render(&shadowPainter, QRectF(), QRectF(-20, -20, mPixmap.width() + 40, mPixmap.height() + 40)); shadowPainter.end(); // we can finish up now mPixmap = QPixmap::fromImage(shadowImage); if (mCapturePointer) { QPoint topLeft = clipRegion.boundingRect().topLeft() - QPoint(20, 20); mPixmap = blendCursorImage(mPixmap, topLeft.x(), topLeft.y(), mPixmap.width(), mPixmap.height()); } emit pixmapChanged(mPixmap); } void X11ImageGrabber::grabActiveWindow() { xcb_window_t activeWindow = KWindowSystem::activeWindow(); // if KWin is available, use the KWin DBus interfaces if (mCaptureDecorations && isKWinAvailable()) { QDBusConnection bus = QDBusConnection::sessionBus(); bus.connect(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot"), QStringLiteral("screenshotCreated"), this, SLOT(KWinDBusScreenshotHelper(quint64))); QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot")); int mask = 1; if (mCapturePointer) { mask |= 1 << 1; } interface.call(QStringLiteral("screenshotForWindow"), (quint64)activeWindow, mask); return; } // otherwise, use the native functionality return grabApplicationWindowHelper(activeWindow); } void X11ImageGrabber::grabWindowUnderCursor() { // if KWin is available, use the KWin DBus interfaces if (mCaptureDecorations && isKWinAvailable()) { QDBusConnection bus = QDBusConnection::sessionBus(); bus.connect(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot"), QStringLiteral("screenshotCreated"), this, SLOT(KWinDBusScreenshotHelper(quint64))); QDBusInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QStringLiteral("org.kde.kwin.Screenshot")); int mask = 1; if (mCapturePointer) { mask |= 1 << 1; } interface.call(QStringLiteral("screenshotWindowUnderCursor"), mask); return; } // else, go native return grabApplicationWindowHelper(getRealWindowUnderCursor()); } void X11ImageGrabber::grabApplicationWindowHelper(xcb_window_t window) { // if the user doesn't want decorations captured, we're in luck. This is // the easiest bit mPixmap = getWindowPixmap(window, mCapturePointer); if (!mCaptureDecorations || window == QX11Info::appRootWindow()) { emit pixmapChanged(mPixmap); return; } // if the user wants the window decorations, things get a little tricky. // we can't simply get a handle to the window manager frame window and // just grab it, because some compositing window managers (yes, kwin // included) do not render the window onto the frame but keep it in a // separate opengl buffer, so grabbing this window is going to simply // give us a transparent image with the frame and titlebar. // all is not lost. what we need to do is grab the image of the entire // desktop, find the geometry of the window including its frame, and // crop the root image accordingly. KWindowInfo info(window, NET::WMFrameExtents); if (info.valid()) { QRect frameGeom = info.frameGeometry(); mPixmap = getWindowPixmap(QX11Info::appRootWindow(), mCapturePointer).copy(frameGeom); } // fallback is window without the frame emit pixmapChanged(mPixmap); } QRect X11ImageGrabber::getApplicationWindowGeometry(xcb_window_t window) { xcb_connection_t *xcbConn = QX11Info::connection(); xcb_get_geometry_cookie_t geomCookie = xcb_get_geometry_unchecked(xcbConn, window); CScopedPointer geomReply(xcb_get_geometry_reply(xcbConn, geomCookie, NULL)); return QRect(geomReply->x, geomReply->y, geomReply->width, geomReply->height); } void X11ImageGrabber::grabCurrentScreen() { mScreenConfigOperation = new KScreen::GetConfigOperation; connect(mScreenConfigOperation, &KScreen::GetConfigOperation::finished, this, &X11ImageGrabber::KScreenCurrentMonitorScreenshotHelper); } void X11ImageGrabber::grabRectangularRegion() { QuickEditor *editor = new QuickEditor(getWindowPixmap(QX11Info::appRootWindow(), false)); connect(editor, &QuickEditor::grabDone, this, &X11ImageGrabber::rectangleSelectionConfirmed); connect(editor, &QuickEditor::grabCancelled, this, &X11ImageGrabber::rectangleSelectionCancelled); } xcb_window_t X11ImageGrabber::getRealWindowUnderCursor() { xcb_connection_t *xcbConn = QX11Info::connection(); xcb_window_t curWin = QX11Info::appRootWindow(); const QByteArray atomName("WM_STATE"); xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(xcbConn, 0, atomName.length(), atomName.constData()); xcb_query_pointer_cookie_t pointerCookie = xcb_query_pointer_unchecked(xcbConn, curWin); CScopedPointer atomReply(xcb_intern_atom_reply(xcbConn, atomCookie, NULL)); CScopedPointer pointerReply(xcb_query_pointer_reply(xcbConn, pointerCookie, NULL)); if (atomReply->atom == XCB_ATOM_NONE) { return QX11Info::appRootWindow(); } // now start testing QStack windowStack; windowStack.push(pointerReply->child); while (!windowStack.isEmpty()) { curWin = windowStack.pop(); // next, check if our window has the WM_STATE peoperty set on // the window. if yes, return the window - we have found it xcb_get_property_cookie_t propertyCookie = xcb_get_property_unchecked(xcbConn, 0, curWin, atomReply->atom, XCB_ATOM_ANY, 0, 0); CScopedPointer propertyReply(xcb_get_property_reply(xcbConn, propertyCookie, NULL)); if (propertyReply->type != XCB_ATOM_NONE) { return curWin; } // if we're here, this means the window is not the real window // we should start looking at its children xcb_query_tree_cookie_t treeCookie = xcb_query_tree_unchecked(xcbConn, curWin); CScopedPointer treeReply(xcb_query_tree_reply(xcbConn, treeCookie, NULL)); xcb_window_t *winChildren = xcb_query_tree_children(treeReply.data()); int winChildrenLength = xcb_query_tree_children_length(treeReply.data()); for (int i = winChildrenLength - 1; i >= 0; i--) { windowStack.push(winChildren[i]); } } // return the window. it has geometry information for a crop return pointerReply->child; } QStack X11ImageGrabber::findAllChildren(xcb_window_t window) { QStack winStack; xcb_connection_t *xcbConn = QX11Info::connection(); xcb_query_tree_cookie_t treeCookie = xcb_query_tree_unchecked(xcbConn, window); CScopedPointer treeReply(xcb_query_tree_reply(xcbConn, treeCookie, NULL)); xcb_window_t *winChildren = xcb_query_tree_children(treeReply.data()); int winChildrenLength = xcb_query_tree_children_length(treeReply.data()); for (int i = winChildrenLength - 1; i >= 0; i--) { winStack.push(winChildren[i]); } return winStack; } xcb_window_t X11ImageGrabber::findParent(xcb_window_t window) { xcb_connection_t *xcbConn = QX11Info::connection(); xcb_query_tree_cookie_t treeCookie = xcb_query_tree_unchecked(xcbConn, window); CScopedPointer treeReply(xcb_query_tree_reply(xcbConn, treeCookie, NULL)); return treeReply->parent; } diff --git a/src/QuickEditor/EditorRoot.qml b/src/QuickEditor/EditorRoot.qml index 428ba1a..f7fb33b 100644 --- a/src/QuickEditor/EditorRoot.qml +++ b/src/QuickEditor/EditorRoot.qml @@ -1,260 +1,260 @@ /* * Copyright (C) 2016 Boudhayan Gupta * * 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. */ import QtQuick 2.5 import QtQuick.Window 2.2 Item { id: editorRoot; objectName: "editorRoot"; // properties and setters property var selection: undefined; property color maskColour: Qt.rgba(0, 0, 0, 0.75); property color strokeColour: Qt.rgba(0.114, 0.6, 0.953, 1); function setInitialSelection(xx, yy, ww, hh) { if (selection) { selection.destroy(); } selection = cropRectangle.createObject(parent, { - "x": xx, - "y": yy, + "x": xx, + "y": yy, "height": hh, - "width": ww + "width": ww }); cropDisplayCanvas.requestPaint(); } // key handlers focus: true; Keys.onReturnPressed: { if (selection) { acceptImage(selection.x, selection.y, selection.width, selection.height); } else { acceptImage(-1, -1, -1, -1); } } Keys.onEscapePressed: { cancelImage(); } // signals signal acceptImage(int x, int y, int width, int height); signal cancelImage(); Image { id: imageBackground; objectName: "imageBackground"; source: "image://snapshot/rawimage"; cache: false; - height: Screen.height; - width: Screen.width; + height: Window.height / Screen.devicePixelRatio; + width: Window.width / Screen.devicePixelRatio; fillMode: Image.PreserveAspectFit; } Canvas { id: cropDisplayCanvas; objectName: "cropDisplayCanvas"; anchors.fill: imageBackground; renderTarget: Canvas.FramebufferObject; renderStrategy: Canvas.Cooperative; onPaint: { // start by getting a context on the canvas and clearing it var ctx = cropDisplayCanvas.getContext("2d"); ctx.clearRect(0, 0, cropDisplayCanvas.width, cropDisplayCanvas.height); // set up the colours ctx.strokeStyle = strokeColour; ctx.fillStyle = maskColour; // draw a sheet over the whole screen ctx.fillRect(0, 0, cropDisplayCanvas.width, cropDisplayCanvas.height); if (selection) { midHelpText.visible = false; bottomHelpText.visible = true; // if we have a selection polygon, cut it out ctx.fillStyle = strokeColour; ctx.fillRect(selection.x, selection.y, selection.width, selection.height); ctx.clearRect(selection.x + 1, selection.y + 1, selection.width - 2, selection.height - 2); if ((selection.width > 20) && (selection.height > 20)) { // top-left handle ctx.beginPath(); ctx.arc(selection.x, selection.y, 8, 0, 0.5 * Math.PI); ctx.lineTo(selection.x, selection.y); ctx.fill(); // top-right handle ctx.beginPath(); ctx.arc(selection.x + selection.width, selection.y, 8, 0.5 * Math.PI, Math.PI); ctx.lineTo(selection.x + selection.width, selection.y); ctx.fill(); // bottom-left handle ctx.beginPath(); ctx.arc(selection.x + selection.width, selection.y + selection.height, 8, Math.PI, 1.5 * Math.PI); ctx.lineTo(selection.x + selection.width, selection.y + selection.height); ctx.fill(); // bottom-right handle ctx.beginPath(); ctx.arc(selection.x, selection.y + selection.height, 8, 1.5 * Math.PI, 2 * Math.PI); ctx.lineTo(selection.x, selection.y + selection.height); ctx.fill(); // top-center handle ctx.beginPath(); ctx.arc(selection.x + selection.width / 2, selection.y, 5, 0, Math.PI); ctx.fill(); // right-center handle ctx.beginPath(); ctx.arc(selection.x + selection.width, selection.y + selection.height / 2, 5, 0.5 * Math.PI, 1.5 * Math.PI); ctx.fill(); // bottom-center handle ctx.beginPath(); ctx.arc(selection.x + selection.width / 2, selection.y + selection.height, 5, Math.PI, 2 * Math.PI); ctx.fill(); // left-center handle ctx.beginPath(); ctx.arc(selection.x, selection.y + selection.height / 2, 5, 1.5 * Math.PI, 0.5 * Math.PI); ctx.fill(); } } else { midHelpText.visible = true; bottomHelpText.visible = false; } } Rectangle { id: midHelpText; objectName: "midHelpText"; height: midHelpTextElement.height + 40; width: midHelpTextElement.width + 40; radius: 10; border.width: 2; border.color: Qt.rgba(0, 0, 0, 1); color: Qt.rgba(1, 1, 1, 0.85); anchors.centerIn: parent; Text { id: midHelpTextElement; text: i18n("Click anywhere on the screen (including here) to start drawing a selection rectangle, or press Esc to quit"); font.pointSize: 12; anchors.centerIn: parent; } } Rectangle { id: bottomHelpText; objectName: "bottomHelpText"; height: bottomHelpTextElement.height + 16; width: bottomHelpTextElement.width + 24; border.width: 1; border.color: Qt.rgba(0, 0, 0, 1); color: Qt.rgba(1, 1, 1, 0.85); anchors.bottom: parent.bottom; anchors.horizontalCenter: parent.horizontalCenter; Text { id: bottomHelpTextElement; text: i18n("To take the screenshot, double-click or press Enter. Right-click to reset the selection, or press Esc to quit"); font.pointSize: 9; anchors.centerIn: parent; } } } MouseArea { anchors.fill: imageBackground; property int startx: 0; property int starty: 0; cursorShape: Qt.CrossCursor; acceptedButtons: Qt.LeftButton | Qt.RightButton; onPressed: { if (selection) { selection.destroy(); } startx = mouse.x; starty = mouse.y; selection = cropRectangle.createObject(parent, { "x": startx, "y": starty, "height": 0, "width": 0 }); } onPositionChanged: { selection.x = Math.min(startx, mouse.x); selection.y = Math.min(starty, mouse.y); selection.width = Math.abs(startx - mouse.x); selection.height = Math.abs(starty - mouse.y); cropDisplayCanvas.requestPaint(); } onClicked: { if ((mouse.button == Qt.RightButton) && (selection)) { selection.destroy(); cropDisplayCanvas.requestPaint(); } } } Component { id: cropRectangle; SelectionRectangle { drawCanvas: cropDisplayCanvas; imageElement: imageBackground; onDoubleClicked: { editorRoot.acceptImage(selection.x, selection.y, selection.width, selection.height); } } } } diff --git a/src/QuickEditor/QuickEditor.cpp b/src/QuickEditor/QuickEditor.cpp index eb73c06..a9bb72f 100644 --- a/src/QuickEditor/QuickEditor.cpp +++ b/src/QuickEditor/QuickEditor.cpp @@ -1,157 +1,143 @@ /* * Copyright (C) 2016 Boudhayan Gupta * * 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 "QuickEditor.h" #include #include #include #include #include #include #include #include #include #include #include #include "SpectacleConfig.h" struct QuickEditor::ImageStore : public QQuickImageProvider { ImageStore(const QPixmap &pixmap) : QQuickImageProvider(QQuickImageProvider::Pixmap), mPixmap(pixmap) {} QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) { Q_UNUSED(id); if (size) { *size = mPixmap.size(); } if (requestedSize.isEmpty()) { return mPixmap; } return mPixmap.scaled(requestedSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); } QPixmap mPixmap; }; struct QuickEditor::QuickEditorPrivate { KDeclarative::KDeclarative *mDecl; QQuickView *mQuickView; QQmlEngine *mQmlEngine; QRect mGrabRect; QSharedPointer mCurrentGrabResult; }; QuickEditor::QuickEditor(const QPixmap &pixmap, QObject *parent) : QObject(parent), mImageStore(new ImageStore(pixmap)), d_ptr(new QuickEditorPrivate) { Q_D(QuickEditor); d->mQmlEngine = new QQmlEngine(); d->mDecl = new KDeclarative::KDeclarative; d->mDecl->setDeclarativeEngine(d->mQmlEngine); d->mDecl->setupBindings(); d->mQmlEngine->addImageProvider(QStringLiteral("snapshot"), mImageStore); d->mQuickView = new QQuickView(d->mQmlEngine, 0); d->mQuickView->setSource(QUrl("qrc:///QuickEditor/EditorRoot.qml")); + + d->mQuickView->setFlags(Qt::BypassWindowManagerHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::Tool); + d->mQuickView->setGeometry(0, 0, pixmap.width(), pixmap.height()); d->mQuickView->showFullScreen(); // connect up the signals QQuickItem *rootItem = d->mQuickView->rootObject(); connect(rootItem, SIGNAL(acceptImage(int, int, int, int)), this, SLOT(acceptImageHandler(int, int, int, int))); connect(rootItem, SIGNAL(cancelImage()), this, SIGNAL(grabCancelled())); // set up initial config SpectacleConfig *config = SpectacleConfig::instance(); if (config->rememberLastRectangularRegion()) { QRect cropRegion = config->cropRegion(); if (!cropRegion.isEmpty()) { QMetaObject::invokeMethod( rootItem, "setInitialSelection", Q_ARG(QVariant, cropRegion.x()), Q_ARG(QVariant, cropRegion.y()), Q_ARG(QVariant, cropRegion.width()), Q_ARG(QVariant, cropRegion.height()) ); } } if (config->useLightRegionMaskColour()) { rootItem->setProperty("maskColour", QColor(255, 255, 255, 192)); rootItem->setProperty("strokeColour", QColor(96, 96, 96, 255)); } } QuickEditor::~QuickEditor() { Q_D(QuickEditor); delete d->mQuickView; delete d->mDecl; delete d->mQmlEngine; delete d_ptr; } void QuickEditor::acceptImageHandler(int x, int y, int width, int height) { Q_D(QuickEditor); if ((x == -1) && (y == -1) && (width == -1) && (height == -1)) { SpectacleConfig::instance()->setCropRegion(QRect()); emit grabCancelled(); return; } d->mGrabRect = QRect(x, y, width, height); SpectacleConfig::instance()->setCropRegion(d->mGrabRect); - QQuickItem *target = d->mQuickView->rootObject()->findChild(QStringLiteral("imageBackground")); - d->mCurrentGrabResult = target->grabToImage(); - if (d->mCurrentGrabResult.isNull()) { - emit grabCancelled(); - return; - } - - connect(d->mCurrentGrabResult.data(), &QQuickItemGrabResult::ready, this, &QuickEditor::grabReadyHandler); -} - -void QuickEditor::grabReadyHandler() -{ - Q_D(QuickEditor); - - QImage croppedImage = d->mCurrentGrabResult->image().copy(d->mGrabRect); - QPixmap croppedPixmap = QPixmap::fromImage(croppedImage); - d->mQuickView->hide(); - emit grabDone(croppedPixmap, d->mGrabRect); + emit grabDone(mImageStore->mPixmap.copy(d->mGrabRect), d->mGrabRect); } diff --git a/src/QuickEditor/QuickEditor.h b/src/QuickEditor/QuickEditor.h index cadd5bb..5cca825 100644 --- a/src/QuickEditor/QuickEditor.h +++ b/src/QuickEditor/QuickEditor.h @@ -1,54 +1,53 @@ /* * Copyright (C) 2016 Boudhayan Gupta * * 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 QUICKEDITOR_H #define QUICKEDITOR_H #include class QuickEditor : public QObject { Q_OBJECT public: explicit QuickEditor(const QPixmap &pixmap, QObject *parent = 0); virtual ~QuickEditor(); signals: void grabDone(const QPixmap &pixmap, const QRect &cropRegion); void grabCancelled(); private slots: void acceptImageHandler(int x, int y, int width, int height); - void grabReadyHandler(); private: struct ImageStore; ImageStore *mImageStore; struct QuickEditorPrivate; Q_DECLARE_PRIVATE(QuickEditor); QuickEditorPrivate *d_ptr; }; #endif // QUICKEDITOR_H diff --git a/src/SpectacleConfig.cpp b/src/SpectacleConfig.cpp index 4055737..b812b00 100644 --- a/src/SpectacleConfig.cpp +++ b/src/SpectacleConfig.cpp @@ -1,224 +1,237 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * 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 "SpectacleConfig.h" #include SpectacleConfig::SpectacleConfig(QObject *parent) : QObject(parent) { mConfig = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); mGeneralConfig = KConfigGroup(mConfig, "General"); mGuiConfig = KConfigGroup(mConfig, "GuiConfig"); } SpectacleConfig::~SpectacleConfig() {} SpectacleConfig* SpectacleConfig::instance() { static SpectacleConfig instance; return &instance; } // lastSaveAsLocation QUrl SpectacleConfig::lastSaveAsLocation() const { return mGeneralConfig.readEntry(QStringLiteral("lastSaveAsLocation"), QUrl::fromUserInput(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation))); } void SpectacleConfig::setLastSaveAsLocation(const QUrl &location) { mGeneralConfig.writeEntry(QStringLiteral("lastSaveAsLocation"), location); mGeneralConfig.sync(); } // cropRegion QRect SpectacleConfig::cropRegion() const { return mGuiConfig.readEntry(QStringLiteral("cropRegion"), QRect()); } void SpectacleConfig::setCropRegion(const QRect ®ion) { mGuiConfig.writeEntry(QStringLiteral("cropRegion"), region); mGuiConfig.sync(); } // onclick bool SpectacleConfig::onClickChecked() const { return mGuiConfig.readEntry(QStringLiteral("onClickChecked"), false); } void SpectacleConfig::setOnClickChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("onClickChecked"), enabled); mGuiConfig.sync(); } // include pointer bool SpectacleConfig::includePointerChecked() const { return mGuiConfig.readEntry(QStringLiteral("includePointer"), true); } void SpectacleConfig::setIncludePointerChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("includePointer"), enabled); mGuiConfig.sync(); } // include decorations bool SpectacleConfig::includeDecorationsChecked() const { return mGuiConfig.readEntry(QStringLiteral("includeDecorations"), true); } void SpectacleConfig::setIncludeDecorationsChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("includeDecorations"), enabled); mGuiConfig.sync(); } // capture transient window only bool SpectacleConfig::captureTransientWindowOnlyChecked() const { return mGuiConfig.readEntry(QStringLiteral("transientOnly"), false); } void SpectacleConfig::setCaptureTransientWindowOnlyChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("transientOnly"), enabled); mGuiConfig.sync(); } // capture delay qreal SpectacleConfig::captureDelay() const { return mGuiConfig.readEntry(QStringLiteral("captureDelay"), 0.0); } void SpectacleConfig::setCaptureDelay(qreal delay) { mGuiConfig.writeEntry(QStringLiteral("captureDelay"), delay); mGuiConfig.sync(); } // capture mode int SpectacleConfig::captureMode() const { return mGuiConfig.readEntry(QStringLiteral("captureModeIndex"), 0); } void SpectacleConfig::setCaptureMode(int index) { mGuiConfig.writeEntry(QStringLiteral("captureModeIndex"), index); mGuiConfig.sync(); } // dynamic save button bool SpectacleConfig::useDynamicSaveButton() const { return mGuiConfig.readEntry(QStringLiteral("dynamicSaveButton"), false); } void SpectacleConfig::setUseDynamicSaveButton(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("dynamicSaveButton"), enabled); mGuiConfig.sync(); } // remember last rectangular region bool SpectacleConfig::rememberLastRectangularRegion() const { return mGuiConfig.readEntry(QStringLiteral("rememberLastRectangularRegion"), false); } void SpectacleConfig::setRememberLastRectangularRegion(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("rememberLastRectangularRegion"), enabled); mGuiConfig.sync(); } // use light region mask colour bool SpectacleConfig::useLightRegionMaskColour() const { return mGuiConfig.readEntry(QStringLiteral("useLightMaskColour"), false); } void SpectacleConfig::setUseLightRegionMaskColour(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("useLightMaskColour"), enabled); mGuiConfig.sync(); } // last used save mode int SpectacleConfig::lastUsedSaveMode() const { return mGuiConfig.readEntry(QStringLiteral("lastUsedSaveMode"), 0); } void SpectacleConfig::setLastUsedSaveMode(int index) { mGuiConfig.writeEntry(QStringLiteral("lastUsedSaveMode"), index); mGuiConfig.sync(); } // autosave filename format QString SpectacleConfig::autoSaveFilenameFormat() const { return mGeneralConfig.readEntry(QStringLiteral("save-filename-format"), QStringLiteral("Screenshot_%Y%M%D_%H%m%S")); } void SpectacleConfig::setAutoSaveFilenameFormat(const QString &format) { mGeneralConfig.writeEntry(QStringLiteral("save-filename-format"), format); mGeneralConfig.sync(); } // autosave location QString SpectacleConfig::autoSaveLocation() const { return mGeneralConfig.readPathEntry(QStringLiteral("default-save-location"), QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); } void SpectacleConfig::setAutoSaveLocation(const QString &location) { mGeneralConfig.writePathEntry(QStringLiteral("default-save-location"), location); mGeneralConfig.sync(); } + +// copy save location to clipboard + +bool SpectacleConfig::copySaveLocationToClipboard() const +{ + return mGeneralConfig.readEntry(QStringLiteral("copySaveLocation"), false); +} + +void SpectacleConfig::setCopySaveLocationToClipboard(bool enabled) +{ + mGeneralConfig.writeEntry(QStringLiteral("copySaveLocation"), enabled); + mGeneralConfig.sync(); +} diff --git a/src/SpectacleConfig.h b/src/SpectacleConfig.h index b85148b..5a91f70 100644 --- a/src/SpectacleConfig.h +++ b/src/SpectacleConfig.h @@ -1,101 +1,104 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * 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 SPECTACLECONFIG_H #define SPECTACLECONFIG_H #include #include #include #include #include class SpectacleConfig : public QObject { Q_OBJECT // singleton-ize the class public: static SpectacleConfig* instance(); private: explicit SpectacleConfig(QObject *parent = 0); virtual ~SpectacleConfig(); SpectacleConfig(SpectacleConfig const&) = delete; void operator= (SpectacleConfig const&) = delete; // everything else public slots: QUrl lastSaveAsLocation() const; void setLastSaveAsLocation(const QUrl &location); QRect cropRegion() const; void setCropRegion(const QRect ®ion); bool onClickChecked() const; void setOnClickChecked(bool enabled); bool includePointerChecked() const; void setIncludePointerChecked(bool enabled); bool includeDecorationsChecked() const; void setIncludeDecorationsChecked(bool enabled); bool captureTransientWindowOnlyChecked() const; void setCaptureTransientWindowOnlyChecked(bool enabled); qreal captureDelay() const; void setCaptureDelay(qreal delay); int captureMode() const; void setCaptureMode(int index); bool useDynamicSaveButton() const; void setUseDynamicSaveButton(bool enabled); bool rememberLastRectangularRegion() const; void setRememberLastRectangularRegion(bool enabled); bool useLightRegionMaskColour() const; void setUseLightRegionMaskColour(bool enabled); int lastUsedSaveMode() const; void setLastUsedSaveMode(int index); QString autoSaveFilenameFormat() const; void setAutoSaveFilenameFormat(const QString &format); QString autoSaveLocation() const; void setAutoSaveLocation(const QString &location); + bool copySaveLocationToClipboard() const; + void setCopySaveLocationToClipboard(bool enabled); + private: KSharedConfigPtr mConfig; KConfigGroup mGeneralConfig; KConfigGroup mGuiConfig; }; #endif // SPECTACLECONFIG_H diff --git a/src/SpectacleCore.cpp b/src/SpectacleCore.cpp index 34744dd..2a6e641 100644 --- a/src/SpectacleCore.cpp +++ b/src/SpectacleCore.cpp @@ -1,274 +1,299 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * 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 "SpectacleCore.h" - +#include "SpectacleConfig.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include #include +#include +#include + +#include "Config.h" +#include "PlatformBackends/DummyImageGrabber.h" +#ifdef XCB_FOUND +#include "PlatformBackends/X11ImageGrabber.h" +#endif SpectacleCore::SpectacleCore(StartMode startMode, ImageGrabber::GrabMode grabMode, QString &saveFileName, qint64 delayMsec, bool notifyOnGrab, QObject *parent) : QObject(parent), mExportManager(ExportManager::instance()), mStartMode(startMode), mNotify(notifyOnGrab), mImageGrabber(nullptr), mMainWindow(nullptr), isGuiInited(false) { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup guiConfig(config, "GuiConfig"); if (!(saveFileName.isEmpty() || saveFileName.isNull())) { if (QDir::isRelativePath(saveFileName)) { saveFileName = QDir::current().absoluteFilePath(saveFileName); } setFilename(saveFileName); } #ifdef XCB_FOUND if (qApp->platformName() == QStringLiteral("xcb")) { mImageGrabber = new X11ImageGrabber; } #endif if (!mImageGrabber) { mImageGrabber = new DummyImageGrabber; } mImageGrabber->setGrabMode(grabMode); mImageGrabber->setCapturePointer(guiConfig.readEntry("includePointer", true)); mImageGrabber->setCaptureDecorations(guiConfig.readEntry("includeDecorations", true)); if ((!(mImageGrabber->onClickGrabSupported())) && (delayMsec < 0)) { delayMsec = 0; } connect(mExportManager, &ExportManager::errorMessage, this, &SpectacleCore::showErrorMessage); connect(this, &SpectacleCore::errorMessage, this, &SpectacleCore::showErrorMessage); connect(mImageGrabber, &ImageGrabber::pixmapChanged, this, &SpectacleCore::screenshotUpdated); connect(mImageGrabber, &ImageGrabber::imageGrabFailed, this, &SpectacleCore::screenshotFailed); - connect(mExportManager, &ExportManager::imageSaved, [&](const QUrl &savedAt) { - emit imageSaved(savedAt.toLocalFile()); - }); + connect(mExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doCopyPath); + connect(mExportManager, &ExportManager::forceNotify, this, &SpectacleCore::doNotify); switch (startMode) { case DBusMode: break; case BackgroundMode: { int msec = (KWindowSystem::compositingActive() ? 200 : 50) + delayMsec; QTimer::singleShot(msec, mImageGrabber, &ImageGrabber::doImageGrab); } break; case GuiMode: initGui(); break; } } SpectacleCore::~SpectacleCore() { if (mMainWindow) { delete mMainWindow; } } // Q_PROPERTY stuff QString SpectacleCore::filename() const { return mFileNameString; } void SpectacleCore::setFilename(const QString &filename) { mFileNameString = filename; mFileNameUrl = QUrl::fromUserInput(filename); } ImageGrabber::GrabMode SpectacleCore::grabMode() const { return mImageGrabber->grabMode(); } void SpectacleCore::setGrabMode(const ImageGrabber::GrabMode &grabMode) { mImageGrabber->setGrabMode(grabMode); } // Slots void SpectacleCore::dbusStartAgent() { qApp->setQuitOnLastWindowClosed(true); if (!(mStartMode == GuiMode)) { mStartMode = GuiMode; return initGui(); } } void SpectacleCore::takeNewScreenshot(const ImageGrabber::GrabMode &mode, const int &timeout, const bool &includePointer, const bool &includeDecorations) { mImageGrabber->setGrabMode(mode); mImageGrabber->setCapturePointer(includePointer); mImageGrabber->setCaptureDecorations(includeDecorations); if (timeout < 0) { mImageGrabber->doOnClickGrab(); return; } // when compositing is enabled, we need to give it enough time for the window // to disappear and all the effects are complete before we take the shot. there's // no way of knowing how long the disappearing effects take, but as per default // settings (and unless the user has set an extremely slow effect), 200 // milliseconds is a good amount of wait time. const int msec = KWindowSystem::compositingActive() ? 200 : 50; QTimer::singleShot(timeout + msec, mImageGrabber, &ImageGrabber::doImageGrab); } void SpectacleCore::showErrorMessage(const QString &errString) { qDebug() << "ERROR: " << errString; if (mStartMode == GuiMode) { KMessageBox::error(0, errString); } } void SpectacleCore::screenshotUpdated(const QPixmap &pixmap) { mExportManager->setPixmap(pixmap); switch (mStartMode) { case BackgroundMode: case DBusMode: { if (mNotify) { connect(mExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doNotify); } QUrl savePath = (mStartMode == BackgroundMode && mFileNameUrl.isValid() && mFileNameUrl.isLocalFile()) ? mFileNameUrl : QUrl(); mExportManager->doSave(savePath); // if we notify, we emit allDone only if the user either dismissed the notification or pressed // the "Open" button, otherwise the app closes before it can react to it. if (!mNotify) { emit allDone(); } } break; case GuiMode: mMainWindow->setScreenshotAndShow(pixmap); } } void SpectacleCore::screenshotFailed() { switch (mStartMode) { case BackgroundMode: showErrorMessage(i18n("Screenshot capture canceled or failed")); case DBusMode: emit grabFailed(); emit allDone(); return; case GuiMode: mMainWindow->show(); } } void SpectacleCore::doNotify(const QUrl &savedAt) { KNotification *notify = new KNotification(QStringLiteral("newScreenshotSaved")); switch(mImageGrabber->grabMode()) { case ImageGrabber::GrabMode::FullScreen: notify->setTitle(i18nc("The entire screen area was captured, heading", "Full Screen Captured")); break; case ImageGrabber::GrabMode::CurrentScreen: notify->setTitle(i18nc("The current screen was captured, heading", "Current Screen Captured")); break; case ImageGrabber::GrabMode::ActiveWindow: notify->setTitle(i18nc("The active window was captured, heading", "Active Window Captured")); break; case ImageGrabber::GrabMode::WindowUnderCursor: notify->setTitle(i18nc("The window under the mouse was captured, heading", "Window Under Cursor Captured")); break; case ImageGrabber::GrabMode::RectangularRegion: notify->setTitle(i18nc("A rectangular region was captured, heading", "Rectangular Region Captured")); break; default: break; } const QString &path = savedAt.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); // a speaking message is prettier than a URL, special case for the default pictures location if (path == QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)) { notify->setText(i18nc("Placeholder is filename", "A screenshot was saved as '%1' to your Pictures folder.", savedAt.fileName())); } else { notify->setText(i18n("A screenshot was saved as '%1' to '%2'.", savedAt.fileName(), path)); } notify->setActions({i18nc("Open the screenshot we just saved", "Open")}); connect(notify, &KNotification::action1Activated, this, [this, savedAt] { new KRun(savedAt, nullptr); - QTimer::singleShot(250, this, &SpectacleCore::allDone); }); connect(notify, &QObject::destroyed, this, &SpectacleCore::allDone); notify->sendEvent(); } +void SpectacleCore::doCopyPath(const QUrl &savedAt) +{ + if (SpectacleConfig::instance()->copySaveLocationToClipboard()) { + qApp->clipboard()->setText(savedAt.toLocalFile()); + } +} + void SpectacleCore::doStartDragAndDrop() { QUrl tempFile = mExportManager->tempSave(); if (!tempFile.isValid()) { return; } QMimeData *mimeData = new QMimeData; mimeData->setUrls(QList { tempFile }); mimeData->setImageData(mExportManager->pixmap()); mimeData->setData(QStringLiteral("application/x-kde-suggestedfilename"), QFile::encodeName(tempFile.fileName())); QDrag *dragHandler = new QDrag(this); dragHandler->setMimeData(mimeData); dragHandler->setPixmap(mExportManager->pixmap().scaled(256, 256, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); dragHandler->exec(); } // Private void SpectacleCore::initGui() { if (!isGuiInited) { mMainWindow = new KSMainWindow(mImageGrabber->onClickGrabSupported()); connect(mMainWindow, &KSMainWindow::newScreenshotRequest, this, &SpectacleCore::takeNewScreenshot); connect(mMainWindow, &KSMainWindow::dragAndDropRequest, this, &SpectacleCore::doStartDragAndDrop); isGuiInited = true; QMetaObject::invokeMethod(mImageGrabber, "doImageGrab", Qt::QueuedConnection); } } diff --git a/src/SpectacleCore.h b/src/SpectacleCore.h index 3a6ce51..97462c1 100644 --- a/src/SpectacleCore.h +++ b/src/SpectacleCore.h @@ -1,127 +1,86 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * 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 KSCORE_H #define KSCORE_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 "Config.h" +#include #include "ExportManager.h" -#include "PlatformBackends/ImageGrabber.h" -#include "PlatformBackends/DummyImageGrabber.h" -#ifdef XCB_FOUND -#include "PlatformBackends/X11ImageGrabber.h" -#endif - #include "Gui/KSMainWindow.h" +#include "PlatformBackends/ImageGrabber.h" class SpectacleCore : public QObject { Q_OBJECT Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged) Q_PROPERTY(ImageGrabber::GrabMode grabMode READ grabMode WRITE setGrabMode NOTIFY grabModeChanged) public: enum StartMode { GuiMode = 0, DBusMode = 1, BackgroundMode = 2 }; explicit SpectacleCore(StartMode startMode, ImageGrabber::GrabMode grabMode, QString &saveFileName, qint64 delayMsec, bool notifyOnGrab, QObject *parent = 0); ~SpectacleCore(); QString filename() const; void setFilename(const QString &filename); ImageGrabber::GrabMode grabMode() const; void setGrabMode(const ImageGrabber::GrabMode &grabMode); signals: void errorMessage(const QString errString); void allDone(); void filenameChanged(QString filename); void grabModeChanged(ImageGrabber::GrabMode mode); void grabFailed(); - void imageSaved(const QString &savedAt); public slots: void takeNewScreenshot(const ImageGrabber::GrabMode &mode, const int &timeout, const bool &includePointer, const bool &includeDecorations); void showErrorMessage(const QString &errString); void screenshotUpdated(const QPixmap &pixmap); void screenshotFailed(); void dbusStartAgent(); void doStartDragAndDrop(); void doNotify(const QUrl &savedAt); + void doCopyPath(const QUrl &savedAt); private: void initGui(); ExportManager *mExportManager; StartMode mStartMode; bool mNotify; QString mFileNameString; QUrl mFileNameUrl; ImageGrabber *mImageGrabber; KSMainWindow *mMainWindow; bool isGuiInited; }; #endif // KSCORE_H