diff --git a/karbon/data/org.kde.karbon.appdata.xml b/karbon/data/org.kde.karbon.appdata.xml
index 75cb0f5203c..58b005520b0 100644
--- a/karbon/data/org.kde.karbon.appdata.xml
+++ b/karbon/data/org.kde.karbon.appdata.xml
@@ -1,395 +1,395 @@
org.kde.karbon.desktop
CC0-1.0
GPL-2.0+
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
Karbon
xxKarbonxx
Karbon
Scalable Graphics
Grafika koja se može skalirati
Gràfics escalables
Gràfics escalables
Škálovatelná grafika
Skalierbare Vektorgrafik
Scalable Graphics
Gráficos escalables
Vektorgraafika
Skaalautuva grafiikka
Graphisme vectoriel
Gráficos de tamaño variábel
Graphicos scalabile
Grafica scalabile
画像の寸法
Vectorafbeeldingen
Skalowalna grafika
Gráficos Vectoriais
Imagens vetoriais
Škálovateľná grafika
Skalbar grafik
Програма для роботи з масштабованою графікою
xxScalable Graphicsxx
可缩放图像
Karbon is a vector drawing application with a user interface that is easy to use, highly customizable and extensible.
That makes Karbon a great application for users starting to explore the world of vector graphics as well as for artists
wanting to create breathtaking vector art.
Karbon je vektorska aplikacija za crtanje s korisničkim sučeljem koje je jednostavno za korištenje , vrlo prilagodljivo i proširivo . To čini Karbon odličnom aplikacijom za korisnike koji počinju istraživati svijet vektorske grafike , kao i za umjetnike koji žele stvoriti prekrasnu vektorsku umjetnost .
El Karbon és una aplicació de dibuix vectorial amb una interfície d'usuari senzilla d'utilitzar, que es pot personalitzar i ampliar molt. Això converteix el Karbon en una gran aplicació per usuaris que comencin a explorar el món dels gràfics vectorials i també pels artistes que volen crear art vectorial que deixi bocabadat.
El Karbon és una aplicació de dibuix vectorial amb una interfície d'usuari senzilla d'utilitzar, que es pot personalitzar i ampliar molt. Això converteix el Karbon en una gran aplicació per usuaris que comencen a explorar el món dels gràfics vectorials i també pels artistes que volen crear art vectorial que deixi bocabadat.
Karbon is a vector drawing application with a user interface that is easy to use, highly customisable and extensible. That makes Karbon a great application for users starting to explore the world of vector graphics as well as for artists wanting to create breathtaking vector art.
Karbon es una aplicación de dibujo vectorial que dispone de una interfaz de usuario intuitiva, altamente personalizable y extensible. Todo ello hace que Karbon sea una gran aplicación para aquellos usuarios que empiezan a explorar el mundo de los gráficos vectoriales, así como para los artistas que desean crear impresionantes piezas de arte vectorial.
Karbon on vektorgraafikarakendus hõlpsasti kasutatava, äärmiselt kohandatava ja laiendatava kasutajaliidesega. See muudab Karboni imeheaks abivahendiks algajale, kes alles uudistab vektorgraafika maailma, aga ka kunstnikele, kes soovivad luua hingematvalt kaunist vektorgraafikat.
Karbon on vektorigrafiikkasovellus, jonka käyttöliittymä on helppo ja laajasti mukautettavissa ja laajennettavissa. Karbon on näin loistava sovellus vektorigrafiikan maailmaan vasta tutustuville käyttäjille samoin kuin henkeäsalpaavaa vektoritaidetta luoville taiteilijoille.
Karbon est une application de dessin vectoriel avec une interface facile à utiliser, personnalisable et extensible. Cela fait de Karbon une application majeure pour les utilisateurs s'initiant au dessin vectoriel et pour les artistes voulant créer des œuvres à couper le souffle.
- Karbon é un aplicativo de debuxo vectorial con unha interface de usuario fácil de usar, personalizar e ampliar. Iso fai de Karbon un gran aplicativo para usuarios que comezan a explorar o mundo das imaxes vectoriais, pero tamén para artistas que queren crear impresionantes obras de arte vectoriais.
+ Karbon é un aplicativo de debuxo vectorial cunha interface de usuario fácil de usar, personalizar e ampliar. Iso fai de Karbon un gran aplicativo para usuarios que comezan a explorar o mundo das imaxes vectoriais, pero tamén para artistas que queren crear impresionantes obras de arte vectoriais.
Karbon è un'applicazione di disegno vettoriale con un'interfaccia utente facile da utilizzare, altamente personalizzabile ed estendibile. Ciò rende Karbon un'ottima applicazione per gli utenti che iniziano a esplorare il mondo della grafica vettoriale così come per gli artisti che vogliono creare straordinarie opere vettoriali.
Karbon is een toepassing voor tekenen met vectoren met een gebruikersinterface dat gemakkelijk is te gebruiken, zeer goed aan te passen en uit te breiden. Dit maakt Karbon een geweldige toepassing voor gebruikers die de wereld van grafiek met vectoren aan het verkennen zijn evenals voor kunstenaars die adembenemende vectorkunst willen maken.
Karbon jest programem do rysowania wektorowego z układem sterowania, który jest łatwy w użyciu, wysoce dostosowywalny i rozszerzalny. To czyni Karbon wspaniałym programem dla użytkowników zaczynających poznawanie świata grafiki wektorowej, a artystów zachęca do tworzenie sztuki wektorowej zapierającej dech w piersiach.
O Karbon é uma aplicação de desenho vectorial com uma interface simples de usar, facilmente configurável e modular. Isto torna o Karbon uma óptima aplicação para os utilizadores que começam a explorar o mundo dos gráficos vectoriais, assim como para os artistas que desejam criar arte vectorial espectacular.
Karbon é um aplicativo de desenho vetorial com uma interface simples de usar, facilmente configurável e expansível. Isso torna o Karbon um ótimo aplicativo para usuários que começam a explorar o mundo dos gráficos vetoriais, assim como para artistas que desejam criar arte vetorial espetacular.
Karbon je vektorová kresliaca aplikácia s používateľským rozhraním jednoduchým na používanie, veľmi prispôsobiteľná a rozšíriteľná. Toto robí z Karbonu výbornú aplikáciu pre používateľov, ktorí začínajú objavovať sveet vektorovej grafiky, ako aj pre umelcov, ktorí chcú vytvárať dych vyrážajúce vektorové umenie.
Karbon är ett vektorritprogram med ett användargränssnitt som är lätt att använda, ytterst anpassningsbart och utökningsbart. Det gör Karbon till ett utmärkt program för användare som börjar utforska vektorgrafikvärlden samt för konstnärer som vill skapa hänförande vektorkonst.
Karbon — програма для векторного малювання з простим, гнучким та придатним до розширення інтерфейсом користувача. Це робить Karbon чудовою програмою для користувачів, які починають знайомитися зі світом векторної графіки, а також для художників, які хочуть створити захопливі векторні твори.
xxKarbon is a vector drawing application with a user interface that is easy to use, highly customizable and extensible. That makes Karbon a great application for users starting to explore the world of vector graphics as well as for artists wanting to create breathtaking vector art.xx
Karbon 是一个矢量绘图程序,具有易用,高度可定制和可扩展的用户界面。它是一个理想的工具,帮助用户探索矢量图形,帮助艺术家创作令人惊叹的矢量艺术作品。
Features:
Svojstva:
Característiques:
Característiques:
Vlastnosti:
Funktionen:
Features:
Características:
Omadused:
Ominaisuuksia:
Fonctionnalités :
Funcionalidades:
Characteristicas
Funzionalità:
機能:
Mogelijkheden:
Możliwości:
Funcionalidades:
Funcionalidades:
Funkcie:
Funktioner:
Можливості:
xxFeatures:xx
功能:
-
Loading support for ODG, SVG, WPG, WMF, EPS/PS
- Učitavanje podrške za ODG, SVG, WPG, WMF, EPS/PS
- Permet llegir ODG, SVG, WPG, WMF, EPS/PS
- Permet llegir ODG, SVG, WPG, WMF, EPS/PS
- Unterstützung für das Laden von ODG, SVG, WPG, WMF, EPS/PS
- Loading support for ODG, SVG, WPG, WMF, EPS/PS
- Admite la carga de ODG, SVG, WPG, WMF, EPS/PS
- ODG, SVG, WPG, WMF, EPS/PS laadimise toetamine
- Lukutuki ODG-, SVG-, WPG-, WMF- ja EPS/PS-muodoista
- Lecture des formats ODG, SVG, WPG, WMF, EPS/PS
- Posibilidade de cargar ODG, SVG, WPG, WMF e EPS/PS.
- Cargante supporto pro ODG, SVG, WPG, WMF, EPS/PS
- Supporto per il caricamento di ODG, SVG, WPG, WMF, EPS/PS
- 読み込み可能な形式: ODG, SVG, WPG, WMF, EPS/PS
- Ondersteuning voor laden van ODG, SVG, WPG, WMF, EPS/PS
- Obsługa wczytywania dla ODG, SVG, WPG, WMF, EPS/PS
- Suporte do carregamento de imagens em ODG, SVG, WPG, WMF, EPS/PS
- Suporte para carregamento de arquivos ODG, SVG, WPG, WMF e EPS/PS
- Podpora čítania pre ODG, SVG, WPG, WMF, EPS/PS
- Stöd för att läsa in ODG, SVG, WPG, WMF, EPS/PS
- Підтримка завантаження даних ODG, SVG, WPG, WMF, EPS/PS.
- xxLoading support for ODG, SVG, WPG, WMF, EPS/PSxx
- 加载支持 ODG,SVG,WPG,WMF,EPS/PS
-
Writing support for ODG, SVG, PNG, PDF, WMF
- Pisanje podrške za ODG, SVG, WPG, WMF, EPS/PS
- Permet gravar ODG, SVG, PNG, PDF, WMF
- Permet gravar ODG, SVG, PNG, PDF, WMF
- Unterstützung für das Schreiben von ODG, SVG, PNG, PDF, WMF
- Writing support for ODG, SVG, PNG, PDF, WMF
- Admite la escritura de ODG, SVG, PNG, PDF, WMF
- ODG, SVG, PNG, PDF, WMF kirjutamine toetamine
- Kirjoitustuki ODG-, SVG-, PNG-, PDF- ja WMF-muotoihin
- Écriture des formats ODG, SVG, PNG, PDF, WMF
- Posibilidade de xerar ODG, SVG, PNG, PDF e WMF.
- Supporto de scriber pro ODG, SVG, PNG, PDF, WMF
- Supporto per la scrittura di ODG, SVG, PNG, PDF, WMF
- 書き込み可能な形式: ODG, SVG, PNG, PDF, WMF
- Ondersteuning voor schrijven van ODG, SVG, PNG, PDF, WMF
- Obsługa zapisywania dla ODG, SVG, PNG, PDF, WMF
- Suporte da gravação de imagens em ODG, SVG, PNG, PDF, WMF
- Suporte para gravação de arquivos ODG, SVG, PNG, PDF e WMF
- Podpora zápisu pre ODG, SVG, PNG, PDF, WMF
- Stöd för att skriva ut ODG, SVG, PNG, PDF, WMF
- Підтримка запису у форматах ODG, SVG, PNG, PDF, WMF.
- xxWriting support for ODG, SVG, PNG, PDF, WMFxx
- 写入支持 ODG,SVG,PNG,PDF,WMF
-
Customizable user interface with freely placeable toolbars and dockers
- Prilagodljiv korisnički interfejs sa gdje se moze slobodno staviti alatne trake i dockers
- Interfície d'usuari que es pot personalitzar que permet situar lliurement barres d'eines i acobladors
- Interfície d'usuari que es pot personalitzar que permet situar lliurement barres d'eines i acobladors
- Anpassungsfähige Bedienungsoberfläche mit frei platzierbaren Werkzeugleisten und andockbaren Fenstern
- Customisable user interface with freely placeable toolbars and dockers
- Interfaz de usuario personalizable con barras de herramientas y paneles que se pueden posicionar libremente en cualquier lugar.
- Kohandatav kasutajaliides vabalt liigutatavate tööriistaribade ja dokkidega
- Mukautettava käyttöliittymä, jonka työkalurivit ja telakat ovat vapaasti sijoitettavissa
- Interface utilisateur personnalisable avec des barres d'outil librement déplaçables.
- Interface de usuario personalizábel con barras de ferramentas e docas que poden recolocarse libremente.
- Interfacie de usator personalisabile con barra de instrumentos e stivatores (dockers) liberemente positionabile
- Interfaccia utente personalizzabile con barre degli strumenti posizionabili liberamente e aree di aggancio
- Aan te passen gebruikersinterface met vrij te plaatsen werkbalken en verankeringen
- Układ sterowania z dowolnie umieszczanymi paskami narzędzi i dokowaniami
- Interface de utilizador configurável com barras e áreas acopláveis livres
- Interface de usuário configurável com barras de ferramentas e áreas acopláveis que podem ser facilmente ajustadas
- Nastaviteľné používateľské rozhranie s voľne umiestniteľnými panelmi nástrojov a dockermi
- Anpassningsbart användargränssnitt med verktygsrader och paneler som kan placeras fritt
- Придатний до налаштовування інтерфейс користувача з довільним розташуванням панелей інструментів та бічних панелей.
- xxCustomizable user interface with freely placeable toolbars and dockersxx
- 可自定义用户界面,包含可自由放置的工具栏和停靠栏
-
Layer docker for easy handling of complex documents including preview thumbnails, support for grouping shapes via
drag and drop, controlling visibility of shapes or locking
- Sloj lučki radnik za jednostavno rukovanje složenim dokumentima , uključujući pregled sličica , podršku za grupiranje oblika putem drag and drop , kontroliranje vidljivost oblika ili zaključavanje
- Acoblador de capa per a una gestió senzilla de documents complexos incloent la vista prèvia de miniatures, permet agrupar formes via arrossegar i deixar anar, controlar la visibilitat de les formes o el bloqueig.
- Acoblador de capa per a una gestió senzilla de documents complexos incloent la vista prèvia de miniatures, permet agrupar formes via arrossegar i deixar anar, controlar la visibilitat de les formes o el bloqueig.
- Layer docker for easy handling of complex documents including preview thumbnails, support for grouping shapes via drag and drop, controlling visibility of shapes or locking
- Panel de capas para un sencillo manejo de documentos complejos, que incluye vista previa de miniaturas y agrupación de figuras mediante la operación de arrastrar y soltar, controlando la visibilidad de las figuras o su bloqueo.
- Kihtide dokk keerukamate dokumentide hõlpsaks töötlemiseks, kaasa arvatud pisipiltide eelvaatlus, kujundite rühmitamise toetamine ainult lohistamisega, kujundite nähtavuse ja lukustamise määramine
- Monimutkaisten tiedostojen helppoon hallintaan tasotelakka, joka sisältää esikatselukuvat, muotojen ryhmittelyn tuen vetämällä ja pudottamalla sekä muotojen näkyvyyden ja lukitsemisen hallinnan
- Conteneurs de calques pour la manipulation facile de documents complexes incluant les vignettes d'aperçu, la prise en charge du groupement de formes par glisser-déposer, contrôle de la visibilité des formes et verrouillage
- Doca de capas para xestionar facilmente documentos complexos. Inclúe miniaturas de vista previa, permite agrupar formas arrastrando e soltando, controlar a visibilidade das formas ou bloquealas.
- Area di aggancio di livelli per una facile gestione di documenti complessi incluse miniature di anteprima, supporto di forme raggruppate tramite il trascinamento e rilascio, controllo della visibilità delle forme e blocco
- Laagverankering voor gemakkelijke behandeling van complexe documenten inclusief voorbeeldminiaturen, ondersteuning voor groeperen van vormen via slepen en laten vallen, besturing van zichtbaarheid van vormen of vastzetten
- Dokowanie warstw dla łatwiejszej obsługi złożonych dokumentów zawierający: podgląd miniatur, obsługę grupowania kształtów poprzez przeciągnij i upuść, sterowanie widocznością kształtów czy blokowanie.
- Área de camadas para o tratamento de documentos complexos, incluindo miniaturas da antevisão, suporte para o agrupamento de formas por arrastamento, o controlo da visibilidade das formas ou o seu bloqueio
- Área de camadas para tratamento de documentos complexos, incluindo miniaturas, suporte a agrupamento de formas com arrastar e soltar, controle de visibilidade das formas ou seu bloqueio
- Docker vrstiev pre ľahkú manipuláciu so zložitými dokumentami vrátane náhľadu miniatúr, podpora pre zoskupovanie tvarov cez drag and drop, ovládanie viditeľnosti tvarov alebo zamykanie
- Lagerpanel för enkel hantering av komplexa dokument inklusive miniatyrbilder för förhandsgranskning, stöd för att gruppera former via drag och släpp, styra eller låsning synlighet av former
- Панель шарів для полегшення обробки складних документів з мініатюрами об’єктів, підтримка групування форм перетягуванням зі скиданням, керування показом форм та блокуванням.
- xxLayer docker for easy handling of complex documents including preview thumbnails, support for grouping shapes via drag and drop, controlling visibility of shapes or lockingxx
- 图层停靠栏用于方便处理复杂文档,包括缩略图预览,支持拖拽组合形状,控制形状的可见性或锁定。
-
Advanced path editing tool with great on-canvas editing capabilities
- Alat za napredno uređivanje puta s velikom mogućnosti uređivanja na platnu
- Eina avançada d'edició de camins amb una gran capacitat d'edició en el llenç
- Eina avançada d'edició de camins amb una gran capacitat d'edició en el llenç
- Advanced path editing tool with great on-canvas editing capabilities
- Herramienta de edición avanzada de rutas con grandes capacidades de edición en el lienzo
- Täiustatud kompleksjoone muutmise tööriist suurepäraste otse lõuendil redigeerimise võimalustega
- Edistynyt polunmuokkaustyökalu, jolla on erinomaiset muokkausominaisuudet
- Outil d'édition de chemin avancé avec des capacités d'édition dans les canevas performantes
- Ferramenta avanzada de edición de rutas cunha gran funcionalidade de edición sobre o lenzo.
- Strumento avanzato di modifica di tracciati con grandi capacità di modifica sul posto
- Geavanceerde padbewerkingsgereedschap met prima bewerkingsmiddelen op het werkblad
- Zaawansowane narzędzia edytowania ścieżki z szerokimi możliwościami edytowania na płótnie
- Ferramenta de edição de caminhos avançada, com grandes capacidade de edição no local
- Ferramenta avançada para edição de caminhos, com ótima capacidade de edição no local
- Pokročilý nástroj na úpravu ciest s výbornými schopnosťami editácie na plátne
- Avancerat konturredigeringsverktyg med utmärkta redigeringsmöjligheter direkt på duken
- Інструмент редагування контурів з чудовими можливостями редагування безпосередньо на полотні.
- xxAdvanced path editing tool with great on-canvas editing capabilitiesxx
- 高级路径编辑工具,以及强大的画布编辑能力
-
Various drawing tools for creating path shapes including a draw path tool, a pencil tool as well as a calligraphy drawing tool
- Razni alati za crtanje za kreiranje put oblike uključujući i put alat nacrtati ,olovka alat , kao i kaligrafija alat za crtanje
- Diverses eines de dibuix per crear formes de camí incloent una eina de dibuix de camins, una eina de llapis i també una eina de dibuix de cal·ligrafia.
- Diverses eines de dibuix per crear formes de camí incloent una eina de dibuix de camins, una eina de llapis i també una eina de dibuix de cal·ligrafia.
- Various drawing tools for creating path shapes including a draw path tool, a pencil tool as well as a calligraphy drawing tool
- Diversas herramientas de dibujo para crear figuras con rutas, entre las que se incluyen una herramienta de dibujo de rutas, una herramienta de pincel y una herramienta de dibujo caligráfico.
- Mitmesugused kompleksjoontest kujundite loomise vahendid, sealhulgas kompleksjoone joonistamise tööriist, pliiatsitööriist ning kalligraafia loomise tööriist
- Eri piirrostyökalut polkumuotojen luontiin: polunpiirto-, kynä- ja kalligrafinen piirrostyökalu
- Outils de dessin variés incluant un outil de création de chemin, un crayon ainsi qu'un outil pour la calligraphie
- Varias ferramentas de debuxo para crear formas de rutas, incluíndo unha ferramenta de debuxo de rutas, unha ferramenta de lapis, e unha ferramenta de debuxo caligráfico.
- Vari strumenti di disegno per creare percorsi e forme che include uno strumento di tracciati, un matita e uno strumento calligrafico
- Verschillende tekengereedschappen voor het maken van padvormen inclusief een tekenhulpmiddel voor paden, een potloodhulpmiddel evenals een tekengereedschap voor kalligrafie
- Różne narzędzia rysownicze do tworzenia kształtów ścieżek uwzględniając w tym: narzędzie rysowania ścieżki, narzędzie ołówka, a także kaligraficzne narzędzia rysownicze
- Diversas ferramentas de desenho para criar formas de caminhos, incluindo uma ferramenta de desenho de caminhos, uma ferramenta de lápis e uma ferramenta de desenho caligráfico
- Diversas ferramentas de desenho para criar formas de caminhos, incluindo uma ferramenta de desenho de caminhos, uma ferramenta de lápis e uma ferramenta de desenho caligráfico
- Rôzne kresliace nástroje na vytváranie tvarov ciest vrátane nástroja na kreslenie cesty, nástroja ceruzky ako aj kaligrafického kresliaceho nástroja
- Diverse ritverktyg för att skapa konturformer som inkluderar ett konturritverktyg, ett pennverktyg samt ett kalligrafiskt ritverktyg
- Різноманітні інструменти малювання для створення контурів, зокрема інструмент малювання контуру, олівець та інструмент каліграфічного малювання.
- xxVarious drawing tools for creating path shapes including a draw path tool, a pencil tool as well as a calligraphy drawing toolxx
- 多种绘图工具用于创作路径形状,包括路径绘制工具,铅笔工具和书法绘制工具
-
Gradient and pattern tools for easy on-canvas editing of gradient and pattern styles
- Gradijent i uzorak alata za jednostavno na - platnu uređivanje gradijent i uzorak stilova
- Eines de degradats i patrons per a una edició senzilla sobre el llenç d'estils de degradats i patrons
- Eines de degradats i patrons per a una edició senzilla sobre el llenç d'estils de degradats i patrons
- Gradient and pattern tools for easy on-canvas editing of gradient and pattern styles
- Herramientas de degradados y patrones para una sencilla edición de estilos de degradados y patrones en el lienzo
- Ülemineku- ja mustritööriistad üleminekute ja mustrite stiilide hõlpsaks redigeerimiseks otse lõuendil
- Työkalut liukuvärien ja täyttökuvioiden helppoon muokkaukseen
- Outils de gradient et de tramage pour l'édition facile de styles de gradients et de trames
- Ferramentas de gradacións e padróns para editar degradados e estilos de padróns facilmente sobre o lenzo.
- Strumenti di gradienti e motivi per una facile modifica sul posto di stili di gradienti e motivi
- Hulpmiddelen voor gradiënten en patronen voor gemakkelijk werken op het werkblad van gradiënten en patroonstijlen
- Narzędzia gradientu i wzorca dla łatwiejszego ich edytowania na płótnie
- Ferramentas de gradientes e padrões para uma edição simples no local dos estilos dos gradientes e dos padrões
- Ferramentas de gradientes e padrões para fácil edição no local dos estilos dos gradientes e dos padrões
- Nástroje na prechody a vzory pre ľahké editovanie na plátne štýlov prechodov a vzorov
- Tonings- och mönsterverktyg för enkelt redigering av tonings- och mönsterstilar direkt på duken
- Інструменти градієнта і візерунка для полегшення редагування на полотні областей з заповненням градієнтом і візерунком.
- xxGradient and pattern tools for easy on-canvas editing of gradient and pattern stylesxx
- 渐变和图案工具可以轻松地在画布上编辑渐变和图案样式
-
Top notch snapping facilities for guided drawing and editing (e.g. snapping to grid, guide lines, path nodes, bounding boxes,
orthogonal positions, intersections of path shapes or extensions of lines and paths)
- Vrhunska snapping objekti za vođene crtanje i uređivanje ( npr snapping na mrežu , vodič linije , staze čvorova , granični kutije , okomita pozicija , preplitanja put oblikuje ili proširenja linija i staze )
- Capacitats òptimes d'ajust per dibuix guiat i edició (p.e. ajust a graella, línies de guia, nodes de camins, caixes contenidores, posicions ortogonals, interseccions de formes de camí o extensions de línies i camins)
- Capacitats òptimes d'ajust per dibuix guiat i edició (p.e. ajust a graella, línies de guia, nodes de camins, caixes contenidores, posicions ortogonals, interseccions de formes de camí o extensions de línies i camins)
- Top notch snapping facilities for guided drawing and editing (e.g. snapping to grid, guide lines, path nodes, bounding boxes, orthogonal positions, intersections of path shapes or extensions of lines and paths)
- Capacidades de ajuste óptimas para el dibujo y la edición guiados (por ejemplo, ajustar a la rejilla, líneas guías, nodos de ruta, cuadros delimitadores, posiciones ortogonales, intersecciones de formas de rutas o extensiones de líneas y rutas)
- Äärmiselt tulusad haardetööriistad juhtjoonte järgi joonistamiseks ja redigeerimiseks (nt tõmme alusvõrgule, juhtjooned, kompleksjoone sõlmed, piirdekastid, kompleksjoontest kujundite lõikekohad jms)
- Huippulaatuinen kiinnitysominaisuus ohjattuun piirtämiseen ja muokkaukseen (esim. kiinnitys ruudukkoon, apuviivoihin, rajauslaatikkoon, kohtisuoraan asemaan, leikkaukseen, polkumuotoon tai viivojen ja polkujen jatkeisiin)
- Fonctionnalités d'alignement top-niveau pour guider le dessin et l'édition (p.ex. alignement sur la grille, chemin nodal, boîtes, positions orthogonales, intersections de formes ou extensions de lignes ou de formes)
- Funcionalidades de axuste de última xeración para editar e debuxar de maneira guiada (por exemplo, axustar á grade, liñas de guía, nodos de rutas, caixas de límites, posicións ortogonais, cruce de formas de rutas ou extensións de liñas e rutas).
- Capacità di alto livello di regolazione per disegno e modifica guidata (ad es. aggancio alla griglia, linee guida, nodi di tracciato, riquadri collegati, posizioni ortgonali, intersezioni di forme di tracciato o estensioni di linee e tracciati)
- Excellente vastklik faciliteiten voor geleid tekenen en bewerken (bijv. vastklikken aan raster, gidslijnen, padpunten, begrenzingsvakken, orthogonale posities, kruispunten van padvormen of extensies van lijnen en paden)
- Możliwości przyciągania z najwyższej półki rysowania i edytowania ze wspomaganiem (np. przyciąganie do siatki, -prowadnic, węzłów ścieżki, pól ograniczających, prostopadłych położeń, przecięć kształtów ścieżek lub wydłużeń linii i ścieżek)
- Capacidades de ajuste óptimas para o desenho e edição guiado (p.ex., ajustes à grelha, linhas-guias, nós de caminhos, áreas envolventes, posições ortogonais, intersecções de formas de caminhos ou extensões de linhas e caminhos)
- Capacidades de melhor ajuste para desenho e edição guiado (p.ex., ajustes à grade, linhas-guias, nós de caminhos, caixas delimitadoras, posições ortogonais, intersecções de formas de caminhos ou extensões de linhas e caminhos)
- Schopnosti prichytenia na vrch pre sprevádzané kreslenie a editovanie (napr. prichytenie k mriežke, pomocné čiary, uzly cesty, hraničné boxy, ortogonálne polohy, priesečníky tvarov cesty alebo rozšírenia čiar a ciest)
- Väldigt bra låsfunktioner för att rita och redigera med styrning (t.ex. låsa till rutnät, styrlinjer, konturnoder, omgivande rutor, ortogonala positioner, korsningar av konturformer eller förlängningar av linjer och konturer)
- Можливості використання прилипання та малювання за напрямними (прилипання до сітки, напрямні, вузли контурів, обмежувальні рамки, ортогональне розташування, перетини та розширення ліній і контурів).
- xxTop notch snapping facilities for guided drawing and editing (e.g. snapping to grid, guide lines, path nodes, bounding boxes, orthogonal positions, intersections of path shapes or extensions of lines and paths)xx
- 顶端对齐工具可以引导绘制和编辑(比如吸附到网格,参考线,路径节点,边界框,正交位置,路径焦点或线的延长线)
-
Includes many predefined shapes including basic shapes like stars, circle/ellipse, rectangle, image
- Uključuje mnoge predefinisane oblike uključujući i osnovne oblike poput zvijezda , krugova / elipsa, pravougaonika, slika
- Inclou moltes formes predefinides bàsiques com estels, cercles/el·lipses, rectangles, imatges
- Inclou moltes formes predefinides bàsiques com estels, cercles/el·lipses, rectangles, imatges
- Enthält viele vordefinierte Objekte einschließlich einfacher Objekte wie Sterne, Kreise, Ellipsen, Rechtecke und Bilder
- Includes many predefined shapes including basic shapes like stars, circle/ellipse, rectangle, image
- Incluye muchas formas predefinidas, entre otras figuras básicas como estrellas, círculos/elipses, rectángulos, imágenes
- Hulk valmiskujundeid, sealhulgas tähed, ringid ja ellipsid, ristkülikud, pildid
- Lukuisia esimääritettyjä muotoja (perusmuodot kuten tähdet, ympyrä ja soikio, nelikulmio, kuva)
- Inclut de nombreuses formes prédéfinies, telles que les étoiles, cercles/ellipses, rectangles, images
- Inclúe moitas formas predefinidas, entre elas formas básicas como estrelas, círculos, elipses, rectángulos e imaxes.
- Include molte forme predefinite tra cui forme base come stelle, cerchi/ellissi, rettangoli e immagini
- Omvat vele voorgedefinieerde vormen inclusief basisvormen als ster, cirkel/ellips, rechthoek, afbeelding
- Zawiera wiele uprzednio stworzonych kształtów, do których można zaliczyć gwiazdy, okręgi, elipsy, prostokąty, obrazy
- Inclui diversas formas predefinidas, incluindo formas básicas como as estrelas, círculos/elipses, rectângulos, imagens
- Inclui diversas formas predefinidas, incluindo formas básicas como estrelas, círculos/elipses, retângulos e imagens
- Obsahuje mnoho preddefinovaných tvarov vrátane základných tvarov ako hviezdy, kružnica/elipsa, obdĺžnik, obrázok
- Innehåller många fördefinierade former inklusive grundläggande former som stjärnor, cirkel/ellips, rektangel, bild
- Передбачено багато готових форм, зокрема базові форми зірки, кола або еліпса, прямокутника та зображення.
- xxIncludes many predefined shapes including basic shapes like stars, circle/ellipse, rectangle, imagexx
- 包含许多预定义形状,如星形,圆形/椭圆,矩形,图像
-
Artistic text shape with support for following path outlines (i.e. text on path)
- Umjetnički oblik tekst s podrškom za sljedeći put naglašava ( tj tekst na putu )
- Formes de text artístic que permeten camins de contorns de seguiment (p.e. text en camins)
- Formes de text artístic que permeten camins de contorns de seguiment (p.e. text en camins)
- Artistic text shape with support for following path outlines (i.e. text on path)
- Forma de texto artístico con soporte para contornos de seguimiento de rutas (es decir, texto sobre una ruta)
- Kunstiline tekstikujund, mis toetab kompleksjooni, st võimaldab asetada teksti kompleksjoonele
- Taiteelliset tekstimuodot polunseurantatuella
- Formes de texte artistiques prenant par exemple en charge la création de texte le long d'une courbe
- Forma de texto artístico que permite seguir o contorno de rutas (é dicir, texto sobre rutas).
- Forme di testo artistico con supporto per le bordature che seguono il tracciato (ad es. testo su tracciato)
- Artistieke tekstvormen met ondersteuning voor het volden van paden (dwz. tekst op een pad)
- Kształty artystycznego tekstu z obsługą dla następujących zarysów ścieżek (tj. tekst na ścieżce)
- Forma de texto artístico com suporte para caminhos de seguimento (i.e., texto ao longo de um caminho)
- Forma de texto artístico com suporte para caminhos de seguimento (isto é, texto no caminho)
- Umelecký textový tvar s podporou pre nasledovacie obrysy cesty (napr. text na ceste)
- Artistisk textform med stöd för att följa konturlinjer (t.ex. text på en kontur)
- Форма художнього тексту для розташування тексту вздовж контуру.
- xxArtistic text shape with support for following path outlines (i.e. text on path)xx
- 艺术字形状,支持跟随路径轮廓(即路径上的文本)
-
Complex path operations and effects like boolean set operations, path flattening, rounding and refining as well as whirl/pinch effects
- Kompleksne staze poslovanja i učinci poput Booleova postavljanje poslovanja , put ravnanje , zaokruživanje i rafiniranje kao vrtlog / za hvatanje učinci
- Operacions complexes de camins i efectes com operacions de conjunts booleans, aplanament de camins, arrodoniment i afinació i també efectes de gir i pessic
- Operacions complexes de camins i efectes com operacions de conjunts booleans, aplanament de camins, arrodoniment i afinació i també efectes de gir i pessic
- Complex path operations and effects like boolean set operations, path flattening, rounding and refining as well as whirl/pinch effects
- Operaciones de ruta complejas y efectos como operaciones booleanas con conjuntos, aplanamiento de rutas, redondeo y refinación así como también efectos de arremolinar/apretar
- Ka keerukamad kompleksjoone operatsioonid ja efektid, näiteks tõeväärtustega operatsioonid, kompleksjoonte lamendamine, ümardamine ja täpsustamine, keerise efekt jms
- Monimutkaiset polku-operaatiot ja -tehosteet kuten totuusarvo-operaatiot, polun tasoitus, pyöristys ja hienosäätö sekä pyörre- ja nipistystehosteet
- Opérations de chemins complexes et effets tels que les opérateurs booléens, mise à plat et ajustements, de même que les effets de spirale et de pincement
- Operacións de rutas complexas e efectos como operacións de grupos de valores booleanos; aplanamento, arredondamento e refinamento de rutas; así como efectos de xiro e picada.
- Operazioni complesse su tracciati ed effetti del tipo operazioni su insiemi booleani, appiattimento di tracciati, arrotondamento e affinamento così come effetti di vortice/pizzico
- Complexe bewerkingen van paden en effecten zoals booleaanse set bewerkingen, afvlakken van paden, afronding en verfijning evenals wervel/put-effecten
- Złożone działania i efekty na ścieżce takie jak działania boolean, spłaszczanie ścieżki, zaokrąglanie, a także efekty polepszające wraz z efektami wiru/rozdmuchania
- Opções complexas com caminhos e efeitos como as operações booleanas com conjuntos, o alisamento de caminhos, o arredondamento e a afinação, para além da ondulação/embate
- Operações complexas com caminhos e efeitos, como operações booleanas com conjuntos, o nivelamento de caminhos, o arredondamento e refinamento, assim como efeitos de ondulação/aperto
- Komplexné cestné operácie a efekty ako logické operácie, splošťovanie cesty, zaobľovanie a zjemňovanie ako aj efekty vírov.
- Komplexa konturoperationer och effekter som Booleska mängdoperationer, konturutplattning, rundning och förfining samt virvla- och klämeffekter
- Складні дії над контурами та ефекти, зокрема набір булівських операцій, спрощення контуру, заокруглення та покращення, ефекти вихору та втягування
- xxComplex path operations and effects like boolean set operations, path flattening, rounding and refining as well as whirl/pinch effectsxx
- 复杂路径操作和效果,例如布尔运算,路径扁平化,简化和优化,以及旋转和揉捏特效
-
Extensible by writing plugins for new tools, shapes and dockers
- Extensible pisanjem dodataka za novim alatima , oblika i modnog brenda Dockers
- Ampliable mitjançant l'escriptura de connectors per eines noves, formes i acobladors
- Ampliable mitjançant l'escriptura de connectors per eines noves, formes i acobladors
- Erweiterbar durch das Schreiben von Modulen für neue Werkzeuge, Objekte und andockbaren Dialogen
- Extensible by writing plugins for new tools, shapes and dockers
- Extensible mediante la escritura de complementos para nuevas herramientas, formas y paneles
- Laiendamisvõimalus pluginate kirjutamise abil uute tööriistade, kujundite ja dokkide tarbeks
- Laajennettavissa kirjoittamalla liitännäisiä uusille työkaluille, muodoille ja telakoille
- Extensible par l'écriture de modules pour de nouveaux outils, formes et conteneurs
- Permite ampliar as súas funcionalidades mediante complementos de ferramentas novas, de formas e de docas.
- Espandibile tramite la scrittura di estensioni per nuovi strumenti, forme e aree di aggancio
- Uit te breiden dor het schrijven van plug-ins voor nieuwe hulpmiddelen, vormen en verankering
- Rozszerzalne wtyczki zapisu dla nowych narzędzi, kształtów i dokowań
- Modular, através da criação de 'plugins' para novas ferramentas, formas e áreas acopláveis
- Expansível através da criação de plugins para novas ferramentas, formas e áreas acopláveis
- Rozšíriteľný pomocou písania pluginov pre nové nástroje, tvary a dockery
- Utökningsbart genom att skriva insticksprogram för nya verktyg, former och paneler
- Можливість розширення додатками нових інструментів, форм та панелей.
- xxExtensible by writing plugins for new tools, shapes and dockersxx
- 可通过工具,形状和停靠栏插件扩展功能
http://www.calligra.org/karbon/
https://bugs.kde.org/enter_bug.cgi?format=guided&product=karbon
https://cdn.kde.org/screenshots/karbon/karbon.png
KDE
calligrakarbon
diff --git a/karbon/stencils/Cisco/atm_fast_gigabit_etherswitch.desktop b/karbon/stencils/Cisco/atm_fast_gigabit_etherswitch.desktop
index 84a009a6897..80ababe58e1 100644
--- a/karbon/stencils/Cisco/atm_fast_gigabit_etherswitch.desktop
+++ b/karbon/stencils/Cisco/atm_fast_gigabit_etherswitch.desktop
@@ -1,32 +1,32 @@
[Desktop Entry]
Name=ATM Fast Gigabit Etherswitch
Name[bg]=Бърз гигабит етернет комутатор ATM
Name[bs]=ATM brzi gigabitni Etherprekidač
Name[ca]=ATM Fast Gigabit Etherswitch
Name[ca@valencia]=ATM Fast Gigabit Etherswitch
Name[da]=ATM Fast Gigabit Etherswitch
Name[de]=ATM-Fast-Gigabit-Ether-Switch
Name[el]=ATM Fast Gigabit Etherswitch
Name[en_GB]=ATM Fast Gigabit Etherswitch
Name[es]=ATM Fast Gigabit Etherswitch
Name[et]=ATM kiire gigabit-ethernetikommutaator
Name[eu]=ATM Fast Gigabit Etherswitch
Name[fr]=ATM Fast Gigabit Etherswitch
-Name[gl]=ATM Fast Gigabit Etherswitch
+Name[gl]=Conmutador de rede rápido de xigabits ATM
Name[hu]=ATM Fast Gigabit Etherswitch
Name[it]=ATM Fast Gigabit Etherswitch
Name[kk]=Жылдам гигабиттік ATM Etherswitch
Name[nb]=ATM rask gigabit ethersvitsj
Name[nds]=ATM-Fast-Gigabit-Etherswitch
Name[nl]=ATM Fast Gigabit Etherswitch
Name[pl]=Przełącznik ATM do Gigabit Ethernet
Name[pt]=Comutador Ethernet Fast Gigabit ATM
Name[pt_BR]=Comutador Ethernet Fast Gigabit ATM
Name[ru]=ATM Быстрый Гигабитный эзернетовский коммутатор (свич)
Name[sk]=ATM Fast Gigabit Etherswitch
Name[sv]=Snabb Gigabit ATM Ethernetväxel
Name[tr]=ATM Fast Gigabit Ethernet Anahtarı
Name[uk]=Швидкісний гігабітовий Etherswitch ATM
Name[x-test]=xxATM Fast Gigabit Etherswitchxx
Name[zh_CN]=ATM Fast Gigabit Etherswitch
Name[zh_TW]=ATM 快速 GB 乙太網路交換器
diff --git a/karbon/stencils/Cisco/gigabit_switch_router_atm_tag.desktop b/karbon/stencils/Cisco/gigabit_switch_router_atm_tag.desktop
index 3ddf4b73ef9..accd1e6bd13 100644
--- a/karbon/stencils/Cisco/gigabit_switch_router_atm_tag.desktop
+++ b/karbon/stencils/Cisco/gigabit_switch_router_atm_tag.desktop
@@ -1,32 +1,32 @@
[Desktop Entry]
Name=Gigabit Switch Router (ATM Tag)
Name[bg]=Гигабит комутиращ маршрутизатор (ATM Tag)
Name[bs]=Gigabit Prekidački Ruter (ATM etiketa)
Name[ca]=Encaminador Gigabit (etiqueta ATM)
Name[ca@valencia]=Encaminador Gigabit (etiqueta ATM)
Name[da]=Gigabit Switch Router (ATM Tag)
Name[de]=Gigabit-Switch-Router (ATM-Tag)
Name[el]=Gigabit Switch Router (ATM Tag)
Name[en_GB]=Gigabit Switch Router (ATM Tag)
Name[es]=Enrutador conmutador Gigabit (ATM Tag)
Name[et]=Gigabit-kommutaator-marsruuter (ATM Tag)
Name[eu]=Gigabit kommutadore-bideratzailea (ATM etiketa)
Name[fr]=Gigabit Switch Router (ATM Tag)
-Name[gl]=Conmutador encamiñador Gigabit (ATM Tag)
+Name[gl]=Conmutador encamiñador de xigabits (ATM Tag)
Name[hu]=Gigabit Switch Router (ATM címke)
Name[it]=Gigabit Switch Router (ATM Tag)
Name[kk]=Гигабиттік қосқыш маршруттауышы (ATM Tag)
Name[nb]=Gigabit svitsjruter (ATM Tag)
Name[nds]=Gigabit-Switch-Nettweger (ATM-Tag)
Name[nl]=Gigabit Switch Router (ATM Tag)
Name[pl]=Router przełączający gigabitowy (znaczniki ATM)
Name[pt]=Encaminhador Gigabit (ATM)
Name[pt_BR]=Roteador/Comutador Gigabit (ATM Tag)
Name[ru]=Маршрутизатор-коммутатор с поддержкой гигабитной сети (метка ATM)
Name[sk]=Gigabit Switch Router (ATM Tag)
Name[sv]=Gigabit växelrouter (ATM-tagg)
Name[tr]=Gigabit Anahtar Yönlendirici (ATM Etiketi)
Name[uk]=Маршрутизатор гігабітового комутатора (ATM Tag)
Name[x-test]=xxGigabit Switch Router (ATM Tag)xx
Name[zh_CN]=千兆交换路由器(ATM Tag)
Name[zh_TW]=GB 交換路由器(ATM Tag)
diff --git a/karbon/stencils/Cisco/label_switch_router.desktop b/karbon/stencils/Cisco/label_switch_router.desktop
index 922f44bf380..6693f059808 100644
--- a/karbon/stencils/Cisco/label_switch_router.desktop
+++ b/karbon/stencils/Cisco/label_switch_router.desktop
@@ -1,31 +1,31 @@
[Desktop Entry]
Name=ATM Tag Sw Gigabit Router
Name[bs]=ATM Tag Sw Gigabit ruter
Name[ca]=Encaminador ATM Tag Sw Gigabit
Name[ca@valencia]=Encaminador ATM Tag Sw Gigabit
Name[da]=ATM Tag Sw Gigabit Router
Name[de]=ATM-Tag SW-Gigabit-Router
Name[el]=ATM Tag Sw Gigabit Router
Name[en_GB]=ATM Tag Sw Gigabit Router
Name[es]=Enrutador Gigabit ATM Tag Sw
Name[et]=ATM Tag kommutaator gigabit-ruuter
Name[eu]=ATM etiketa Sw Gigabit bideratzailea
Name[fr]=ATM Tag Sw Gigabit Router
-Name[gl]=Encamiñador Gigabit por software ATM Tag
+Name[gl]=Encamiñador de xigabits por software ATM Tag
Name[hu]=ATM Tag Sw Gigabit Router
Name[it]=ATM Tag Sw Gigabit Router
Name[kk]=ATM Tag Sw гигабиттіік маршруттауышы
Name[nb]=ATM tagg-svitsj Gigabit-ruter
Name[nds]=ATM-Tag SW Gigabit-Nettweger
Name[nl]=ATM Tag Sw gigabitrouter
Name[pl]=Router przełączający znaczniki ATM gigabitowy
Name[pt]=Encaminhador Gigabit SW ATM
Name[pt_BR]=Roteador Gigabit SW ATM
Name[ru]=Софтовый маршрутизатор с поддержкой гигабитной сети (метка ATM)
Name[sk]=ATM Tag Sw Gigabit Router
Name[sv]=Gigabit ATM-taggväxelrouter
Name[tr]=ATM Tag Sw Gigabit Yönlendirici
Name[uk]=Гігабітовий маршрутизатор ATM Tag Sw
Name[x-test]=xxATM Tag Sw Gigabit Routerxx
Name[zh_CN]=ATM 光纤交换路由器
Name[zh_TW]=ATM Tag Sw GB 路由器
diff --git a/libs/main/KoDocument.cpp b/libs/main/KoDocument.cpp
index 7bda1d356c3..68a9b3d8dc9 100644
--- a/libs/main/KoDocument.cpp
+++ b/libs/main/KoDocument.cpp
@@ -1,2684 +1,2695 @@
/* This file is part of the KDE project
* Copyright (C) 1998, 1999 Torben Weis
* Copyright (C) 2000-2005 David Faure
* Copyright (C) 2007-2008 Thorsten Zachmann
* Copyright (C) 2010-2012 Boudewijn Rempt
* Copyright (C) 2011 Inge Wallin
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoDocument.h"
#include "KoMainWindow.h" // XXX: remove
#include // XXX: remove
#include // XXX: remove
#include "KoComponentData.h"
#include "KoPart.h"
#include "KoEmbeddedDocumentSaver.h"
#include "KoFilterManager.h"
#include "KoFileDialog.h"
#include "KoDocumentInfo.h"
#include "KoView.h"
#include "KoOdfReadStore.h"
#include "KoOdfWriteStore.h"
#include "KoXmlNS.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
#ifndef QT_NO_DBUS
#include
#include
#endif
// Define the protocol used here for embedded documents' URL
// This used to "store" but QUrl didn't like it,
// so let's simply make it "tar" !
#define STORE_PROTOCOL "tar"
// The internal path is a hack to make QUrl happy and for document children
#define INTERNAL_PROTOCOL "intern"
#define INTERNAL_PREFIX "intern:/"
// Warning, keep it sync in koStore.cc
#include
#include "KoUndoStackAction.h"
#include
using namespace std;
/**********************************************************
*
* KoDocument
*
**********************************************************/
namespace {
class DocumentProgressProxy : public KoProgressProxy {
public:
KoMainWindow *m_mainWindow;
DocumentProgressProxy(KoMainWindow *mainWindow)
: m_mainWindow(mainWindow)
{
}
~DocumentProgressProxy() {
// signal that the job is done
setValue(-1);
}
int maximum() const {
return 100;
}
void setValue(int value) {
if (m_mainWindow) {
m_mainWindow->slotProgress(value);
}
}
void setRange(int /*minimum*/, int /*maximum*/) {
}
void setFormat(const QString &/*format*/) {
}
};
}
//static
QString KoDocument::newObjectName()
{
static int s_docIFNumber = 0;
QString name; name.setNum(s_docIFNumber++); name.prepend("document_");
return name;
}
class Q_DECL_HIDDEN KoDocument::Private
{
public:
Private(KoDocument *document, KoPart *part) :
document(document),
parentPart(part),
docInfo(0),
docRdf(0),
progressUpdater(0),
progressProxy(0),
profileStream(0),
filterManager(0),
specialOutputFlag(0), // default is native format
isImporting(false),
isExporting(false),
password(QString()),
modifiedAfterAutosave(false),
autosaving(false),
shouldCheckAutoSaveFile(true),
autoErrorHandlingEnabled(true),
backupFile(true),
backupPath(QString()),
doNotSaveExtDoc(false),
storeInternal(false),
isLoading(false),
undoStack(0),
modified(false),
readwrite(true),
+ alwaysAllowSaving(false),
disregardAutosaveFailure(false)
{
m_job = 0;
m_statJob = 0;
m_uploadJob = 0;
m_saveOk = false;
m_waitForSave = false;
m_duringSaveAs = false;
m_bTemp = false;
m_bAutoDetectedMime = false;
confirmNonNativeSave[0] = true;
confirmNonNativeSave[1] = true;
if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
unit = KoUnit::Inch;
} else {
unit = KoUnit::Centimeter;
}
}
KoDocument *document;
KoPart *const parentPart;
KoDocumentInfo *docInfo;
KoDocumentRdfBase *docRdf;
KoProgressUpdater *progressUpdater;
KoProgressProxy *progressProxy;
QTextStream *profileStream;
QTime profileReferenceTime;
KoUnit unit;
KoFilterManager *filterManager; // The filter-manager to use when loading/saving [for the options]
QByteArray mimeType; // The actual mimetype of the document
QByteArray outputMimeType; // The mimetype to use when saving
bool confirmNonNativeSave [2]; // used to pop up a dialog when saving for the
// first time if the file is in a foreign format
// (Save/Save As, Export)
int specialOutputFlag; // See KoFileDialog in koMainWindow.cc
bool isImporting;
bool isExporting; // File --> Import/Export vs File --> Open/Save
QString password; // The password used to encrypt an encrypted document
QTimer autoSaveTimer;
QString lastErrorMessage; // see openFile()
int autoSaveDelay; // in seconds, 0 to disable.
bool modifiedAfterAutosave;
bool autosaving;
bool shouldCheckAutoSaveFile; // usually true
bool autoErrorHandlingEnabled; // usually true
bool backupFile;
QString backupPath;
bool doNotSaveExtDoc; // makes it possible to save only internally stored child documents
bool storeInternal; // Store this doc internally even if url is external
bool isLoading; // True while loading (openUrl is async)
QList versionInfo;
KUndo2Stack *undoStack;
KoGridData gridData;
KoGuidesData guidesData;
bool isEmpty;
KoPageLayout pageLayout;
KIO::FileCopyJob * m_job;
KIO::StatJob * m_statJob;
KIO::FileCopyJob * m_uploadJob;
QUrl m_originalURL; // for saveAs
QString m_originalFilePath; // for saveAs
bool m_saveOk : 1;
bool m_waitForSave : 1;
bool m_duringSaveAs : 1;
bool m_bTemp: 1; // If @p true, @p m_file is a temporary file that needs to be deleted later.
bool m_bAutoDetectedMime : 1; // whether the mimetype in the arguments was detected by the part itself
QUrl m_url; // Remote (or local) url - the one displayed to the user.
QString m_file; // Local file - the only one the part implementation should deal with.
QEventLoop m_eventLoop;
bool modified;
bool readwrite;
-
+ bool alwaysAllowSaving;
bool disregardAutosaveFailure;
bool openFile()
{
DocumentProgressProxy *progressProxy = 0;
if (!document->progressProxy()) {
KoMainWindow *mainWindow = 0;
if (parentPart->mainWindows().count() > 0) {
mainWindow = parentPart->mainWindows()[0];
}
progressProxy = new DocumentProgressProxy(mainWindow);
document->setProgressProxy(progressProxy);
}
document->setUrl(m_url);
bool ok = document->openFile();
if (progressProxy) {
document->setProgressProxy(0);
delete progressProxy;
}
return ok;
}
bool openLocalFile()
{
m_bTemp = false;
// set the mimetype only if it was not already set (for example, by the host application)
if (mimeType.isEmpty()) {
// get the mimetype of the file
// using findByUrl() to avoid another string -> url conversion
QMimeType mime = QMimeDatabase().mimeTypeForUrl(m_url);
if (mime.isValid()) {
mimeType = mime.name().toLatin1();
m_bAutoDetectedMime = true;
}
}
const bool ret = openFile();
if (ret) {
emit document->completed();
} else {
emit document->canceled(QString());
}
return ret;
}
void openRemoteFile()
{
m_bTemp = true;
// Use same extension as remote file. This is important for mimetype-determination (e.g. koffice)
QString fileName = m_url.fileName();
QFileInfo fileInfo(fileName);
QString ext = fileInfo.completeSuffix();
QString extension;
if (!ext.isEmpty() && m_url.query().isNull()) // not if the URL has a query, e.g. cgi.pl?something
extension = '.'+ext; // keep the '.'
QTemporaryFile tempFile(QDir::tempPath() + "/" + qAppName() + QLatin1String("_XXXXXX") + extension);
tempFile.setAutoRemove(false);
tempFile.open();
m_file = tempFile.fileName();
const QUrl destURL = QUrl::fromLocalFile( m_file );
KIO::JobFlags flags = KIO::DefaultFlags;
flags |= KIO::Overwrite;
m_job = KIO::file_copy(m_url, destURL, 0600, flags);
#ifndef QT_NO_DBUS
KJobWidgets::setWindow(m_job, 0);
if (m_job->ui()) {
KJobWidgets::setWindow(m_job, parentPart->currentMainwindow());
}
#endif
QObject::connect(m_job, SIGNAL(result(KJob*)), document, SLOT(_k_slotJobFinished(KJob*)));
QObject::connect(m_job, SIGNAL(mimetype(KIO::Job*,QString)), document, SLOT(_k_slotGotMimeType(KIO::Job*,QString)));
}
// Set m_file correctly for m_url
void prepareSaving()
{
// Local file
if ( m_url.isLocalFile() )
{
if ( m_bTemp ) // get rid of a possible temp file first
{ // (happens if previous url was remote)
QFile::remove( m_file );
m_bTemp = false;
}
m_file = m_url.toLocalFile();
}
else
{ // Remote file
// We haven't saved yet, or we did but locally - provide a temp file
if ( m_file.isEmpty() || !m_bTemp )
{
QTemporaryFile tempFile;
tempFile.setAutoRemove(false);
tempFile.open();
m_file = tempFile.fileName();
m_bTemp = true;
}
// otherwise, we already had a temp file
}
}
void _k_slotJobFinished( KJob * job )
{
Q_ASSERT( job == m_job );
m_job = 0;
if (job->error())
emit document->canceled( job->errorString() );
else {
if ( openFile() ) {
emit document->completed();
}
else {
emit document->canceled(QString());
}
}
}
void _k_slotStatJobFinished(KJob * job)
{
Q_ASSERT(job == m_statJob);
m_statJob = 0;
// this could maybe confuse some apps? So for now we'll just fallback to KIO::get
// and error again. Well, maybe this even helps with wrong stat results.
if (!job->error()) {
const QUrl localUrl = static_cast(job)->mostLocalUrl();
if (localUrl.isLocalFile()) {
m_file = localUrl.toLocalFile();
openLocalFile();
return;
}
}
openRemoteFile();
}
void _k_slotGotMimeType(KIO::Job *job, const QString &mime)
{
// kDebug(1000) << mime;
Q_ASSERT(job == m_job); Q_UNUSED(job);
// set the mimetype only if it was not already set (for example, by the host application)
if (mimeType.isEmpty()) {
mimeType = mime.toLatin1();
m_bAutoDetectedMime = true;
}
}
void _k_slotUploadFinished( KJob * )
{
if (m_uploadJob->error())
{
QFile::remove(m_uploadJob->srcUrl().toLocalFile());
m_uploadJob = 0;
if (m_duringSaveAs) {
document->setUrl(m_originalURL);
m_file = m_originalFilePath;
}
}
else
{
::org::kde::KDirNotify::emitFilesAdded(QUrl::fromLocalFile(m_url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()));
m_uploadJob = 0;
document->setModified( false );
emit document->completed();
m_saveOk = true;
}
m_duringSaveAs = false;
m_originalURL = QUrl();
m_originalFilePath.clear();
if (m_waitForSave) {
m_eventLoop.quit();
}
}
};
KoDocument::KoDocument(KoPart *parent, KUndo2Stack *undoStack)
: d(new Private(this, parent))
{
Q_ASSERT(parent);
d->isEmpty = true;
d->filterManager = new KoFilterManager(this, d->progressUpdater);
connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
setAutoSave(defaultAutoSave());
setObjectName(newObjectName());
d->docInfo = new KoDocumentInfo(this);
d->pageLayout.width = 0;
d->pageLayout.height = 0;
d->pageLayout.topMargin = 0;
d->pageLayout.bottomMargin = 0;
d->pageLayout.leftMargin = 0;
d->pageLayout.rightMargin = 0;
d->undoStack = undoStack;
d->undoStack->setParent(this);
KConfigGroup cfgGrp(d->parentPart->componentData().config(), "Undo");
d->undoStack->setUndoLimit(cfgGrp.readEntry("UndoLimit", 1000));
connect(d->undoStack, SIGNAL(indexChanged(int)), this, SLOT(slotUndoStackIndexChanged(int)));
}
KoDocument::~KoDocument()
{
d->autoSaveTimer.disconnect(this);
d->autoSaveTimer.stop();
d->parentPart->deleteLater();
delete d->filterManager;
delete d;
}
KoPart *KoDocument::documentPart() const
{
return d->parentPart;
}
bool KoDocument::exportDocument(const QUrl &_url)
{
bool ret;
d->isExporting = true;
//
// Preserve a lot of state here because we need to restore it in order to
// be able to fake a File --> Export. Can't do this in saveFile() because,
// for a start, KParts has already set url and m_file and because we need
// to restore the modified flag etc. and don't want to put a load on anyone
// reimplementing saveFile() (Note: importDocument() and exportDocument()
// will remain non-virtual).
//
QUrl oldURL = url();
QString oldFile = localFilePath();
bool wasModified = isModified();
QByteArray oldMimeType = mimeType();
// save...
ret = saveAs(_url);
//
// This is sooooo hacky :(
// Hopefully we will restore enough state.
//
debugMain << "Restoring KoDocument state to before export";
// always restore url & m_file because KParts has changed them
// (regardless of failure or success)
setUrl(oldURL);
setLocalFilePath(oldFile);
// on successful export we need to restore modified etc. too
// on failed export, mimetype/modified hasn't changed anyway
if (ret) {
setModified(wasModified);
d->mimeType = oldMimeType;
}
d->isExporting = false;
return ret;
}
bool KoDocument::saveFile()
{
debugMain << "doc=" << url().url();
// Save it to be able to restore it after a failed save
const bool wasModified = isModified();
// The output format is set by koMainWindow, and by openFile
QByteArray outputMimeType = d->outputMimeType;
if (outputMimeType.isEmpty()) {
outputMimeType = d->outputMimeType = nativeFormatMimeType();
debugMain << "Empty output mime type, saving to" << outputMimeType;
}
QApplication::setOverrideCursor(Qt::WaitCursor);
if (backupFile()) {
if (url().isLocalFile())
KBackup::backupFile(url().toLocalFile(), d->backupPath);
else {
KIO::UDSEntry entry;
if (KIO::NetAccess::stat(url(),
entry,
d->parentPart->currentMainwindow())) { // this file exists => backup
emit statusBarMessage(i18n("Making backup..."));
QUrl backup;
if (d->backupPath.isEmpty())
backup = url();
else
backup = QUrl::fromLocalFile(d->backupPath + '/' + url().fileName());
backup.setPath(backup.path() + QString::fromLatin1("~"));
KFileItem item(entry, url());
Q_ASSERT(item.name() == url().fileName());
KIO::FileCopyJob *job = KIO::file_copy(url(), backup, item.permissions(), KIO::Overwrite | KIO::HideProgressInfo);
job->exec();
}
}
}
emit statusBarMessage(i18n("Saving..."));
qApp->processEvents();
bool ret = false;
bool suppressErrorDialog = false;
if (!isNativeFormat(outputMimeType)) {
debugMain << "Saving to format" << outputMimeType << "in" << localFilePath();
// Not native format : save using export filter
KoFilter::ConversionStatus status = d->filterManager->exportDocument(localFilePath(), outputMimeType);
ret = status == KoFilter::OK;
suppressErrorDialog = (status == KoFilter::UserCancelled || status == KoFilter::BadConversionGraph);
} else {
// Native format => normal save
Q_ASSERT(!localFilePath().isEmpty());
ret = saveNativeFormat(localFilePath());
}
if (ret) {
d->undoStack->setClean();
removeAutoSaveFiles();
// Restart the autosave timer
// (we don't want to autosave again 2 seconds after a real save)
setAutoSave(d->autoSaveDelay);
}
QApplication::restoreOverrideCursor();
if (!ret) {
if (!suppressErrorDialog) {
if (errorMessage().isEmpty()) {
KMessageBox::error(0, i18n("Could not save\n%1", localFilePath()));
} else if (errorMessage() != "USER_CANCELED") {
KMessageBox::error(0, i18n("Could not save %1\nReason: %2", localFilePath(), errorMessage()));
}
}
// couldn't save file so this new URL is invalid
// FIXME: we should restore the current document's true URL instead of
// setting it to nothing otherwise anything that depends on the URL
// being correct will not work (i.e. the document will be called
// "Untitled" which may not be true)
//
// Update: now the URL is restored in KoMainWindow but really, this
// should still be fixed in KoDocument/KParts (ditto for file).
// We still resetURL() here since we may or may not have been called
// by KoMainWindow - Clarence
resetURL();
// As we did not save, restore the "was modified" status
setModified(wasModified);
}
if (ret) {
d->mimeType = outputMimeType;
setConfirmNonNativeSave(isExporting(), false);
}
emit clearStatusBarMessage();
if (ret) {
KNotification *notify = new KNotification("DocumentSaved");
notify->setText(i18n("Document %1 saved", url().url()));
notify->addContext("url", url().url());
QTimer::singleShot(0, notify, SLOT(sendEvent()));
}
return ret;
}
QByteArray KoDocument::mimeType() const
{
return d->mimeType;
}
void KoDocument::setMimeType(const QByteArray & mimeType)
{
d->mimeType = mimeType;
}
void KoDocument::setOutputMimeType(const QByteArray & mimeType, int specialOutputFlag)
{
d->outputMimeType = mimeType;
d->specialOutputFlag = specialOutputFlag;
}
QByteArray KoDocument::outputMimeType() const
{
return d->outputMimeType;
}
int KoDocument::specialOutputFlag() const
{
return d->specialOutputFlag;
}
bool KoDocument::confirmNonNativeSave(const bool exporting) const
{
// "exporting ? 1 : 0" is different from "exporting" because a bool is
// usually implemented like an "int", not "unsigned : 1"
return d->confirmNonNativeSave [ exporting ? 1 : 0 ];
}
void KoDocument::setConfirmNonNativeSave(const bool exporting, const bool on)
{
d->confirmNonNativeSave [ exporting ? 1 : 0] = on;
}
bool KoDocument::saveInBatchMode() const
{
return d->filterManager->getBatchMode();
}
void KoDocument::setSaveInBatchMode(const bool batchMode)
{
d->filterManager->setBatchMode(batchMode);
}
bool KoDocument::isImporting() const
{
return d->isImporting;
}
bool KoDocument::isExporting() const
{
return d->isExporting;
}
void KoDocument::setCheckAutoSaveFile(bool b)
{
d->shouldCheckAutoSaveFile = b;
}
void KoDocument::setAutoErrorHandlingEnabled(bool b)
{
d->autoErrorHandlingEnabled = b;
}
bool KoDocument::isAutoErrorHandlingEnabled() const
{
return d->autoErrorHandlingEnabled;
}
void KoDocument::slotAutoSave()
{
if (d->modified && d->modifiedAfterAutosave && !d->isLoading) {
// Give a warning when trying to autosave an encrypted file when no password is known (should not happen)
if (d->specialOutputFlag == SaveEncrypted && d->password.isNull()) {
// That advice should also fix this error from occurring again
emit statusBarMessage(i18n("The password of this encrypted document is not known. Autosave aborted! Please save your work manually."));
} else {
connect(this, SIGNAL(sigProgress(int)), d->parentPart->currentMainwindow(), SLOT(slotProgress(int)));
emit statusBarMessage(i18n("Autosaving..."));
d->autosaving = true;
bool ret = saveNativeFormat(autoSaveFile(localFilePath()));
setModified(true);
if (ret) {
d->modifiedAfterAutosave = false;
d->autoSaveTimer.stop(); // until the next change
}
d->autosaving = false;
emit clearStatusBarMessage();
disconnect(this, SIGNAL(sigProgress(int)), d->parentPart->currentMainwindow(), SLOT(slotProgress(int)));
if (!ret && !d->disregardAutosaveFailure) {
emit statusBarMessage(i18n("Error during autosave! Partition full?"));
}
}
}
}
void KoDocument::setReadWrite(bool readwrite)
{
d->readwrite = readwrite;
setAutoSave(d->autoSaveDelay);
// XXX: this doesn't belong in KoDocument
foreach(KoView *view, d->parentPart->views()) {
view->updateReadWrite(readwrite);
}
foreach(KoMainWindow *mainWindow, d->parentPart->mainWindows()) {
mainWindow->setReadWrite(readwrite);
}
}
void KoDocument::setAutoSave(int delay)
{
d->autoSaveDelay = delay;
if (isReadWrite() && d->autoSaveDelay > 0)
d->autoSaveTimer.start(d->autoSaveDelay * 1000);
else
d->autoSaveTimer.stop();
}
KoDocumentInfo *KoDocument::documentInfo() const
{
return d->docInfo;
}
KoDocumentRdfBase *KoDocument::documentRdf() const
{
return d->docRdf;
}
void KoDocument::setDocumentRdf(KoDocumentRdfBase *rdfDocument)
{
delete d->docRdf;
d->docRdf = rdfDocument;
}
bool KoDocument::isModified() const
{
return d->modified;
}
bool KoDocument::saveNativeFormat(const QString & file)
{
d->lastErrorMessage.clear();
KoStore::Backend backend = KoStore::Auto;
if (d->specialOutputFlag == SaveAsDirectoryStore) {
backend = KoStore::Directory;
debugMain << "Saving as uncompressed XML, using directory store.";
}
#ifdef QCA2
else if (d->specialOutputFlag == SaveEncrypted) {
backend = KoStore::Encrypted;
debugMain << "Saving using encrypted backend.";
}
#endif
else if (d->specialOutputFlag == SaveAsFlatXML) {
debugMain << "Saving as a flat XML file.";
QFile f(file);
if (f.open(QIODevice::WriteOnly | QIODevice::Text)) {
bool success = saveToStream(&f);
f.close();
return success;
} else
return false;
}
debugMain << "KoDocument::saveNativeFormat nativeFormatMimeType=" << nativeFormatMimeType();
// OLD: bool oasis = d->specialOutputFlag == SaveAsOASIS;
// OLD: QCString mimeType = oasis ? nativeOasisMimeType() : nativeFormatMimeType();
QByteArray mimeType = d->outputMimeType;
debugMain << "KoDocument::savingTo mimeType=" << mimeType;
QByteArray nativeOasisMime = nativeOasisMimeType();
bool oasis = !mimeType.isEmpty() && (mimeType == nativeOasisMime || mimeType == nativeOasisMime + "-template" || mimeType.startsWith("application/vnd.oasis.opendocument"));
// TODO: use std::auto_ptr or create store on stack [needs API fixing],
// to remove all the 'delete store' in all the branches
KoStore *store = KoStore::createStore(file, KoStore::Write, mimeType, backend);
if (d->specialOutputFlag == SaveEncrypted && !d->password.isNull())
store->setPassword(d->password);
if (store->bad()) {
d->lastErrorMessage = i18n("Could not create the file for saving"); // more details needed?
delete store;
return false;
}
if (oasis) {
return saveNativeFormatODF(store, mimeType);
} else {
return saveNativeFormatCalligra(store);
}
}
bool KoDocument::saveNativeFormatODF(KoStore *store, const QByteArray &mimeType)
{
debugMain << "Saving to OASIS format";
// Tell KoStore not to touch the file names
KoOdfWriteStore odfStore(store);
KoXmlWriter *manifestWriter = odfStore.manifestWriter(mimeType);
KoEmbeddedDocumentSaver embeddedSaver;
SavingContext documentContext(odfStore, embeddedSaver);
if (!saveOdf(documentContext)) {
debugMain << "saveOdf failed";
odfStore.closeManifestWriter(false);
delete store;
return false;
}
// Save embedded objects
if (!embeddedSaver.saveEmbeddedDocuments(documentContext)) {
debugMain << "save embedded documents failed";
odfStore.closeManifestWriter(false);
delete store;
return false;
}
if (store->open("meta.xml")) {
if (!d->docInfo->saveOasis(store) || !store->close()) {
odfStore.closeManifestWriter(false);
delete store;
return false;
}
manifestWriter->addManifestEntry("meta.xml", "text/xml");
} else {
d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("meta.xml"));
odfStore.closeManifestWriter(false);
delete store;
return false;
}
if (d->docRdf && !d->docRdf->saveOasis(store, manifestWriter)) {
d->lastErrorMessage = i18n("Not able to write RDF metadata. Partition full?");
odfStore.closeManifestWriter(false);
delete store;
return false;
}
if (store->open("Thumbnails/thumbnail.png")) {
if (!saveOasisPreview(store, manifestWriter) || !store->close()) {
d->lastErrorMessage = i18n("Error while trying to write '%1'. Partition full?", QString("Thumbnails/thumbnail.png"));
odfStore.closeManifestWriter(false);
delete store;
return false;
}
// No manifest entry!
} else {
d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("Thumbnails/thumbnail.png"));
odfStore.closeManifestWriter(false);
delete store;
return false;
}
if (!d->versionInfo.isEmpty()) {
if (store->open("VersionList.xml")) {
KoStoreDevice dev(store);
KoXmlWriter *xmlWriter = KoOdfWriteStore::createOasisXmlWriter(&dev,
"VL:version-list");
for (int i = 0; i < d->versionInfo.size(); ++i) {
KoVersionInfo *version = &d->versionInfo[i];
xmlWriter->startElement("VL:version-entry");
xmlWriter->addAttribute("VL:title", version->title);
xmlWriter->addAttribute("VL:comment", version->comment);
xmlWriter->addAttribute("VL:creator", version->saved_by);
xmlWriter->addAttribute("dc:date-time", version->date.toString(Qt::ISODate));
xmlWriter->endElement();
}
xmlWriter->endElement(); // root element
xmlWriter->endDocument();
delete xmlWriter;
store->close();
manifestWriter->addManifestEntry("VersionList.xml", "text/xml");
for (int i = 0; i < d->versionInfo.size(); ++i) {
KoVersionInfo *version = &d->versionInfo[i];
store->addDataToFile(version->data, "Versions/" + version->title);
}
} else {
d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("VersionList.xml"));
odfStore.closeManifestWriter(false);
delete store;
return false;
}
}
// Write out manifest file
if (!odfStore.closeManifestWriter()) {
d->lastErrorMessage = i18n("Error while trying to write '%1'. Partition full?", QString("META-INF/manifest.xml"));
delete store;
return false;
}
// Remember the given password, if necessary
if (store->isEncrypted() && !d->isExporting)
d->password = store->password();
delete store;
return true;
}
bool KoDocument::saveNativeFormatCalligra(KoStore *store)
{
debugMain << "Saving root";
if (store->open("root")) {
KoStoreDevice dev(store);
if (!saveToStream(&dev) || !store->close()) {
debugMain << "saveToStream failed";
delete store;
return false;
}
} else {
d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml"));
delete store;
return false;
}
if (store->open("documentinfo.xml")) {
QDomDocument doc = KoDocument::createDomDocument("document-info"
/*DTD name*/, "document-info" /*tag name*/, "1.1");
doc = d->docInfo->save(doc);
KoStoreDevice dev(store);
QByteArray s = doc.toByteArray(); // this is already Utf8!
(void)dev.write(s.data(), s.size());
(void)store->close();
}
if (store->open("preview.png")) {
// ### TODO: missing error checking (The partition could be full!)
savePreview(store);
(void)store->close();
}
if (!completeSaving(store)) {
delete store;
return false;
}
debugMain << "Saving done of url:" << url().url();
if (!store->finalize()) {
delete store;
return false;
}
// Success
delete store;
return true;
}
bool KoDocument::saveToStream(QIODevice *dev)
{
QDomDocument doc = saveXML();
// Save to buffer
QByteArray s = doc.toByteArray(); // utf8 already
dev->open(QIODevice::WriteOnly);
int nwritten = dev->write(s.data(), s.size());
if (nwritten != (int)s.size())
warnMain << "wrote " << nwritten << "- expected" << s.size();
return nwritten == (int)s.size();
}
QString KoDocument::checkImageMimeTypes(const QString &mimeType, const QUrl &url) const
{
if (!url.isLocalFile()) return mimeType;
if (url.toLocalFile().endsWith(".kpp")) return "image/png";
QStringList imageMimeTypes;
imageMimeTypes << "image/jpeg"
<< "image/x-psd" << "image/photoshop" << "image/x-photoshop" << "image/x-vnd.adobe.photoshop" << "image/vnd.adobe.photoshop"
<< "image/x-portable-pixmap" << "image/x-portable-graymap" << "image/x-portable-bitmap"
<< "application/pdf"
<< "image/x-exr"
<< "image/x-xcf"
<< "image/x-eps"
<< "image/png"
<< "image/bmp" << "image/x-xpixmap" << "image/gif" << "image/x-xbitmap"
<< "image/tiff"
<< "image/jp2";
if (!imageMimeTypes.contains(mimeType)) return mimeType;
QFile f(url.toLocalFile());
f.open(QIODevice::ReadOnly);
QByteArray ba = f.read(qMin(f.size(), (qint64)512)); // should be enough for images
QMimeType mime = QMimeDatabase().mimeTypeForData(ba);
f.close();
return mime.name();
}
// Called for embedded documents
bool KoDocument::saveToStore(KoStore *_store, const QString & _path)
{
debugMain << "Saving document to store" << _path;
_store->pushDirectory();
// Use the path as the internal url
if (_path.startsWith(STORE_PROTOCOL))
setUrl(QUrl(_path));
else // ugly hack to pass a relative URI
setUrl(QUrl(INTERNAL_PREFIX + _path));
// In the current directory we're the king :-)
if (_store->open("root")) {
KoStoreDevice dev(_store);
if (!saveToStream(&dev)) {
_store->close();
return false;
}
if (!_store->close())
return false;
}
if (!completeSaving(_store))
return false;
// Now that we're done leave the directory again
_store->popDirectory();
debugMain << "Saved document to store";
return true;
}
bool KoDocument::saveOasisPreview(KoStore *store, KoXmlWriter *manifestWriter)
{
const QPixmap pix = generatePreview(QSize(128, 128));
if (pix.isNull())
return true; //no thumbnail to save, but the process succeeded
QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly));
if (preview.isNull())
return false; //thumbnail to save, but the process failed
// ### TODO: freedesktop.org Thumbnail specification (date...)
KoStoreDevice io(store);
if (!io.open(QIODevice::WriteOnly))
return false;
if (! preview.save(&io, "PNG", 0))
return false;
io.close();
manifestWriter->addManifestEntry("Thumbnails/thumbnail.png", "image/png");
return true;
}
bool KoDocument::savePreview(KoStore *store)
{
QPixmap pix = generatePreview(QSize(256, 256));
const QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly));
KoStoreDevice io(store);
if (!io.open(QIODevice::WriteOnly))
return false;
if (! preview.save(&io, "PNG")) // ### TODO What is -9 in quality terms?
return false;
io.close();
return true;
}
QPixmap KoDocument::generatePreview(const QSize& size)
{
qreal docWidth, docHeight;
int pixmapSize = qMax(size.width(), size.height());
if (d->pageLayout.width > 1.0) {
docWidth = d->pageLayout.width / 72 * KoDpi::dpiX();
docHeight = d->pageLayout.height / 72 * KoDpi::dpiY();
} else {
// If we don't have a page layout, just draw the top left hand corner
docWidth = 500.0;
docHeight = 500.0;
}
qreal ratio = docWidth / docHeight;
int previewWidth, previewHeight;
if (ratio > 1.0) {
previewWidth = (int) pixmapSize;
previewHeight = (int)(pixmapSize / ratio);
} else {
previewWidth = (int)(pixmapSize * ratio);
previewHeight = (int) pixmapSize;
}
QPixmap pix((int)docWidth, (int)docHeight);
pix.fill(QColor(245, 245, 245));
QRect rc(0, 0, pix.width(), pix.height());
QPainter p;
p.begin(&pix);
paintContent(p, rc);
p.end();
return pix.scaled(QSize(previewWidth, previewHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
QString KoDocument::autoSaveFile(const QString & path) const
{
QString retval;
// Using the extension allows to avoid relying on the mime magic when opening
QMimeType mime = QMimeDatabase().mimeTypeForName(nativeFormatMimeType());
if (! mime.isValid()) {
qFatal("It seems your installation is broken/incomplete because we failed to load the native mimetype \"%s\".", nativeFormatMimeType().constData());
}
const QString extension = mime.preferredSuffix();
if (path.isEmpty()) {
// Never saved?
#ifdef Q_OS_WIN
// On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921)
retval = QString("%1/.%2-%3-%4-autosave%5").arg(QDir::tempPath()).arg(d->parentPart->componentData().componentName()).arg(QApplication::applicationPid()).arg(objectName()).arg(extension);
#else
// On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file
retval = QString("%1/.%2-%3-%4-autosave%5").arg(QDir::homePath()).arg(d->parentPart->componentData().componentName()).arg(QApplication::applicationPid()).arg(objectName()).arg(extension);
#endif
} else {
QUrl url = QUrl::fromLocalFile(path);
Q_ASSERT(url.isLocalFile());
QString dir = QFileInfo(url.toLocalFile()).absolutePath();
QString filename = url.fileName();
retval = QString("%1.%2-autosave%3").arg(dir).arg(filename).arg(extension);
}
return retval;
}
void KoDocument::setDisregardAutosaveFailure(bool disregardFailure)
{
d->disregardAutosaveFailure = disregardFailure;
}
bool KoDocument::importDocument(const QUrl &_url)
{
bool ret;
debugMain << "url=" << _url.url();
d->isImporting = true;
// open...
ret = openUrl(_url);
// reset url & m_file (kindly? set by KoParts::openUrl()) to simulate a
// File --> Import
if (ret) {
debugMain << "success, resetting url";
resetURL();
setTitleModified();
}
d->isImporting = false;
return ret;
}
bool KoDocument::openUrl(const QUrl &_url)
{
debugMain << "url=" << _url.url();
d->lastErrorMessage.clear();
// Reimplemented, to add a check for autosave files and to improve error reporting
if (!_url.isValid()) {
d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ?
return false;
}
abortLoad();
QUrl url(_url);
bool autosaveOpened = false;
d->isLoading = true;
if (url.isLocalFile() && d->shouldCheckAutoSaveFile) {
QString file = url.toLocalFile();
QString asf = autoSaveFile(file);
if (QFile::exists(asf)) {
//debugMain <<"asf=" << asf;
// ## TODO compare timestamps ?
int res = KMessageBox::warningYesNoCancel(0,
i18n("An autosaved file exists for this document.\nDo you want to open it instead?"));
switch (res) {
case KMessageBox::Yes :
url.setPath(asf);
autosaveOpened = true;
break;
case KMessageBox::No :
QFile::remove(asf);
break;
default: // Cancel
d->isLoading = false;
return false;
}
}
}
bool ret = openUrlInternal(url);
if (autosaveOpened) {
resetURL(); // Force save to act like 'Save As'
setReadWrite(true); // enable save button
setModified(true);
}
else {
d->parentPart->addRecentURLToAllMainWindows(_url);
if (ret) {
// Detect readonly local-files; remote files are assumed to be writable, unless we add a KIO::stat here (async).
KFileItem file(url, mimeType(), KFileItem::Unknown);
setReadWrite(file.isWritable());
}
}
return ret;
}
// It seems that people have started to save .docx files as .doc and
// similar for xls and ppt. So let's make a small replacement table
// here and see if we can open the files anyway.
static const struct MimetypeReplacement {
const char *typeFromName; // If the mime type from the name is this...
const char *typeFromContents; // ...and findByFileContents() reports this type...
const char *useThisType; // ...then use this type for real.
} replacementMimetypes[] = {
// doc / docx
{
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
},
{
"application/msword",
"application/zip",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
},
{
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/msword",
"application/msword"
},
{
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/x-ole-storage",
"application/msword"
},
// xls / xlsx
{
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
},
{
"application/vnd.ms-excel",
"application/zip",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
},
{
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-excel",
"application/vnd.ms-excel"
},
{
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/x-ole-storage",
"application/vnd.ms-excel"
},
// ppt / pptx
{
"application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
},
{
"application/vnd.ms-powerpoint",
"application/zip",
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
},
{
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.ms-powerpoint",
"application/vnd.ms-powerpoint"
},
{
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/x-ole-storage",
"application/vnd.ms-powerpoint"
}
};
bool KoDocument::openFile()
{
//debugMain <<"for" << localFilePath();
if (!QFile::exists(localFilePath())) {
QApplication::restoreOverrideCursor();
if (d->autoErrorHandlingEnabled)
// Maybe offer to create a new document with that name ?
KMessageBox::error(0, i18n("The file %1 does not exist.", localFilePath()));
d->isLoading = false;
return false;
}
QApplication::setOverrideCursor(Qt::WaitCursor);
d->specialOutputFlag = 0;
QByteArray _native_format = nativeFormatMimeType();
QUrl u = QUrl::fromLocalFile(localFilePath());
QString typeName = mimeType();
if (typeName.isEmpty()) {
typeName = QMimeDatabase().mimeTypeForUrl(u).name();
}
// for images, always check content.
typeName = checkImageMimeTypes(typeName, u);
// Sometimes it seems that arguments().mimeType() contains a much
// too generic mime type. In that case, let's try some educated
// guesses based on what we know about file extension.
//
// FIXME: Should we just ignore this and always call
// KMimeType::findByUrl()? David Faure says that it's
// impossible for findByUrl() to fail to initiate the
// mimetype for "*.doc" to application/msword. This hints
// that we should do that. But why does it happen like
// this at all?
if (typeName == "application/zip") {
QString filename = u.fileName();
// None of doc, xls or ppt are really zip files. But docx,
// xlsx and pptx are. This miscategorization seems to only
// crop up when there is a, say, docx file saved as doc. The
// conversion to the docx mimetype will happen below.
if (filename.endsWith(".doc"))
typeName = "application/msword";
else if (filename.endsWith(".xls"))
typeName = "application/vnd.ms-excel";
else if (filename.endsWith(".ppt"))
typeName = "application/vnd.ms-powerpoint";
// Potentially more guesses here...
} else if (typeName == "application/x-ole-storage") {
QString filename = u.fileName();
// None of docx, xlsx or pptx are really OLE files. But doc,
// xls and ppt are. This miscategorization seems to only crop
// up when there is a, say, doc file saved as docx. The
// conversion to the doc mimetype will happen below.
if (filename.endsWith(".docx"))
typeName = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
else if (filename.endsWith(".xlsx"))
typeName = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
else if (filename.endsWith(".pptx"))
typeName = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
// Potentially more guesses here...
}
//debugMain << "mimetypes 3:" << typeName;
// In some cases docx files are saved as doc and similar. We have
// a small hardcoded table for those cases. Check if this is
// applicable here.
for (uint i = 0; i < sizeof(replacementMimetypes) / sizeof(struct MimetypeReplacement); ++i) {
const MimetypeReplacement *replacement = &replacementMimetypes[i];
if (typeName == replacement->typeFromName) {
//debugMain << "found potential replacement target:" << typeName;
// QT5TODO: this needs a new look with the different behaviour of QMimeDatabase
QString typeFromContents = QMimeDatabase().mimeTypeForUrl(u).name();
//debugMain << "found potential replacement:" << typeFromContents;
if (typeFromContents == replacement->typeFromContents) {
typeName = replacement->useThisType;
//debugMain << "So really use this:" << typeName;
break;
}
}
}
//debugMain << "mimetypes 4:" << typeName;
// Allow to open backup files, don't keep the mimetype application/x-trash.
if (typeName == "application/x-trash") {
QString path = u.path();
QMimeDatabase db;
QMimeType mime = db.mimeTypeForName(typeName);
const QStringList patterns = mime.isValid() ? mime.globPatterns() : QStringList();
// Find the extension that makes it a backup file, and remove it
for (QStringList::ConstIterator it = patterns.begin(); it != patterns.end(); ++it) {
QString ext = *it;
if (!ext.isEmpty() && ext[0] == '*') {
ext.remove(0, 1);
if (path.endsWith(ext)) {
path.chop(ext.length());
break;
}
}
}
typeName = db.mimeTypeForFile(path, QMimeDatabase::MatchExtension).name();
}
// Special case for flat XML files (e.g. using directory store)
if (u.fileName() == "maindoc.xml" || u.fileName() == "content.xml" || typeName == "inode/directory") {
typeName = _native_format; // Hmm, what if it's from another app? ### Check mimetype
d->specialOutputFlag = SaveAsDirectoryStore;
debugMain << "loading" << u.fileName() << ", using directory store for" << localFilePath() << "; typeName=" << typeName;
}
debugMain << localFilePath() << "type:" << typeName;
QString importedFile = localFilePath();
// create the main progress monitoring object for loading, this can
// contain subtasks for filtering and loading
KoProgressProxy *progressProxy = 0;
if (d->progressProxy) {
progressProxy = d->progressProxy;
}
d->progressUpdater = new KoProgressUpdater(progressProxy,
KoProgressUpdater::Unthreaded,
d->profileStream);
d->progressUpdater->setReferenceTime(d->profileReferenceTime);
d->progressUpdater->start(100, i18n("Opening Document"));
setupOpenFileSubProgress();
if (!isNativeFormat(typeName.toLatin1())) {
KoFilter::ConversionStatus status;
importedFile = d->filterManager->importDocument(localFilePath(), typeName, status);
if (status != KoFilter::OK) {
QApplication::restoreOverrideCursor();
QString msg;
switch (status) {
case KoFilter::OK: break;
case KoFilter::FilterCreationError:
msg = i18n("Could not create the filter plugin"); break;
case KoFilter::CreationError:
msg = i18n("Could not create the output document"); break;
case KoFilter::FileNotFound:
msg = i18n("File not found"); break;
case KoFilter::StorageCreationError:
msg = i18n("Cannot create storage"); break;
case KoFilter::BadMimeType:
msg = i18n("Bad MIME type"); break;
case KoFilter::EmbeddedDocError:
msg = i18n("Error in embedded document"); break;
case KoFilter::WrongFormat:
msg = i18n("Format not recognized"); break;
case KoFilter::NotImplemented:
msg = i18n("Not implemented"); break;
case KoFilter::ParsingError:
msg = i18n("Parsing error"); break;
case KoFilter::PasswordProtected:
msg = i18n("Document is password protected"); break;
case KoFilter::InvalidFormat:
msg = i18n("Invalid file format"); break;
case KoFilter::InternalError:
case KoFilter::UnexpectedEOF:
case KoFilter::UnexpectedOpcode:
case KoFilter::StupidError: // ?? what is this ??
case KoFilter::UsageError:
msg = i18n("Internal error"); break;
case KoFilter::OutOfMemory:
msg = i18n("Out of memory"); break;
case KoFilter::FilterEntryNull:
msg = i18n("Empty Filter Plugin"); break;
case KoFilter::NoDocumentCreated:
msg = i18n("Trying to load into the wrong kind of document"); break;
case KoFilter::DownloadFailed:
msg = i18n("Failed to download remote file"); break;
case KoFilter::UserCancelled:
case KoFilter::BadConversionGraph:
// intentionally we do not prompt the error message here
break;
default: msg = i18n("Unknown error"); break;
}
if (d->autoErrorHandlingEnabled && !msg.isEmpty()) {
QString errorMsg(i18n("Could not open %2.\nReason: %1.\n%3", msg, prettyPathOrUrl(), errorMessage()));
KMessageBox::error(0, errorMsg);
}
d->isLoading = false;
delete d->progressUpdater;
d->progressUpdater = 0;
return false;
}
d->isEmpty = false;
debugMain << "importedFile" << importedFile << "status:" << static_cast(status);
}
QApplication::restoreOverrideCursor();
bool ok = true;
if (!importedFile.isEmpty()) { // Something to load (tmp or native file) ?
// The filter, if any, has been applied. It's all native format now.
if (!loadNativeFormat(importedFile)) {
ok = false;
if (d->autoErrorHandlingEnabled) {
showLoadingErrorDialog();
}
}
}
if (importedFile != localFilePath()) {
// We opened a temporary file (result of an import filter)
// Set document URL to empty - we don't want to save in /tmp !
// But only if in readwrite mode (no saving problem otherwise)
// --
// But this isn't true at all. If this is the result of an
// import, then importedFile=temporary_file.kwd and
// file/m_url=foreignformat.ext so m_url is correct!
// So don't resetURL() or else the caption won't be set when
// foreign files are opened (an annoying bug).
// - Clarence
//
#if 0
if (isReadWrite())
resetURL();
#endif
// remove temp file - uncomment this to debug import filters
if (!importedFile.isEmpty()) {
#ifndef NDEBUG
if (!getenv("CALLIGRA_DEBUG_FILTERS"))
#endif
QFile::remove(importedFile);
}
}
if (ok) {
setMimeTypeAfterLoading(typeName);
KNotification *notify = new KNotification("DocumentLoaded");
notify->setText(i18n("Document %1 loaded", url().url()));
notify->addContext("url", url().url());
QTimer::singleShot(0, notify, SLOT(sendEvent()));
d->parentPart->deleteOpenPane();
}
if (progressUpdater()) {
QPointer updater
= progressUpdater()->startSubtask(1, "clear undo stack");
updater->setProgress(0);
undoStack()->clear();
updater->setProgress(100);
}
delete d->progressUpdater;
d->progressUpdater = 0;
d->isLoading = false;
return ok;
}
KoProgressUpdater *KoDocument::progressUpdater() const
{
return d->progressUpdater;
}
void KoDocument::setProgressProxy(KoProgressProxy *progressProxy)
{
d->progressProxy = progressProxy;
}
KoProgressProxy* KoDocument::progressProxy() const
{
if (!d->progressProxy) {
KoMainWindow *mainWindow = 0;
if (d->parentPart->mainwindowCount() > 0) {
mainWindow = d->parentPart->mainWindows()[0];
}
d->progressProxy = new DocumentProgressProxy(mainWindow);
}
return d->progressProxy;
}
// shared between openFile and koMainWindow's "create new empty document" code
void KoDocument::setMimeTypeAfterLoading(const QString& mimeType)
{
d->mimeType = mimeType.toLatin1();
d->outputMimeType = d->mimeType;
const bool needConfirm = !isNativeFormat(d->mimeType);
setConfirmNonNativeSave(false, needConfirm);
setConfirmNonNativeSave(true, needConfirm);
}
// The caller must call store->close() if loadAndParse returns true.
bool KoDocument::oldLoadAndParse(KoStore *store, const QString& filename, KoXmlDocument& doc)
{
//debugMain <<"Trying to open" << filename;
if (!store->open(filename)) {
warnMain << "Entry " << filename << " not found!";
d->lastErrorMessage = i18n("Could not find %1", filename);
return false;
}
// Error variables for QDomDocument::setContent
QString errorMsg;
int errorLine, errorColumn;
bool ok = doc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn);
store->close();
if (!ok) {
errorMain << "Parsing error in " << filename << "! Aborting!" << endl
<< " In line: " << errorLine << ", column: " << errorColumn << endl
<< " Error message: " << errorMsg << endl;
d->lastErrorMessage = i18n("Parsing error in %1 at line %2, column %3\nError message: %4"
, filename , errorLine, errorColumn ,
QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0,
QCoreApplication::UnicodeUTF8));
return false;
}
debugMain << "File" << filename << " loaded and parsed";
return true;
}
bool KoDocument::loadNativeFormat(const QString & file_)
{
QString file = file_;
QFileInfo fileInfo(file);
if (!fileInfo.exists()) { // check duplicated from openUrl, but this is useful for templates
d->lastErrorMessage = i18n("The file %1 does not exist.", file);
return false;
}
if (!fileInfo.isFile()) {
file += "/content.xml";
QFileInfo fileInfo2(file);
if (!fileInfo2.exists() || !fileInfo2.isFile()) {
d->lastErrorMessage = i18n("%1 is not a file." , file_);
return false;
}
}
QApplication::setOverrideCursor(Qt::WaitCursor);
debugMain << file;
QFile in;
bool isRawXML = false;
if (d->specialOutputFlag != SaveAsDirectoryStore) { // Don't try to open a directory ;)
in.setFileName(file);
if (!in.open(QIODevice::ReadOnly)) {
QApplication::restoreOverrideCursor();
d->lastErrorMessage = i18n("Could not open the file for reading (check read permissions).");
return false;
}
char buf[6];
buf[5] = 0;
int pos = 0;
do {
if (in.read(buf + pos , 1) < 1) {
QApplication::restoreOverrideCursor();
in.close();
d->lastErrorMessage = i18n("Could not read the beginning of the file.");
return false;
}
if (QChar(buf[pos]).isSpace())
continue;
pos++;
} while (pos < 5);
isRawXML = (qstrnicmp(buf, "lastErrorMessage = i18n("parsing error in the main document at line %1, column %2\nError message: %3", errorLine, errorColumn, i18n(errorMsg.toUtf8()));
res = false;
}
QApplication::restoreOverrideCursor();
in.close();
d->isEmpty = false;
return res;
} else { // It's a calligra store (tar.gz, zip, directory, etc.)
in.close();
return loadNativeFormatFromStore(file);
}
}
bool KoDocument::loadNativeFormatFromStore(const QString& file)
{
KoStore::Backend backend = (d->specialOutputFlag == SaveAsDirectoryStore) ? KoStore::Directory : KoStore::Auto;
KoStore *store = KoStore::createStore(file, KoStore::Read, "", backend);
if (store->bad()) {
d->lastErrorMessage = i18n("Not a valid Calligra file: %1", file);
delete store;
QApplication::restoreOverrideCursor();
return false;
}
// Remember that the file was encrypted
if (d->specialOutputFlag == 0 && store->isEncrypted() && !d->isImporting)
d->specialOutputFlag = SaveEncrypted;
const bool success = loadNativeFormatFromStoreInternal(store);
// Retrieve the password after loading the file, only then is it guaranteed to exist
if (success && store->isEncrypted() && !d->isImporting)
d->password = store->password();
delete store;
return success;
}
bool KoDocument::loadNativeFormatFromStore(QByteArray &data)
{
bool succes;
KoStore::Backend backend = (d->specialOutputFlag == SaveAsDirectoryStore) ? KoStore::Directory : KoStore::Auto;
QBuffer buffer(&data);
KoStore *store = KoStore::createStore(&buffer, KoStore::Read, "", backend);
if (store->bad()) {
delete store;
return false;
}
// Remember that the file was encrypted
if (d->specialOutputFlag == 0 && store->isEncrypted() && !d->isImporting)
d->specialOutputFlag = SaveEncrypted;
succes = loadNativeFormatFromStoreInternal(store);
// Retrieve the password after loading the file, only then is it guaranteed to exist
if (succes && store->isEncrypted() && !d->isImporting)
d->password = store->password();
delete store;
return succes;
}
bool KoDocument::loadNativeFormatFromStoreInternal(KoStore *store)
{
bool oasis = true;
if (oasis && store->hasFile("manifest.rdf") && d->docRdf) {
d->docRdf->loadOasis(store);
}
// OASIS/OOo file format?
if (store->hasFile("content.xml")) {
// We could check the 'mimetype' file, but let's skip that and be tolerant.
if (!loadOasisFromStore(store)) {
QApplication::restoreOverrideCursor();
return false;
}
} else if (store->hasFile("root") || store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml)
oasis = false;
KoXmlDocument doc = KoXmlDocument(true);
bool ok = oldLoadAndParse(store, "root", doc);
if (ok)
ok = loadXML(doc, store);
if (!ok) {
QApplication::restoreOverrideCursor();
return false;
}
} else {
errorMain << "ERROR: No maindoc.xml" << endl;
d->lastErrorMessage = i18n("Invalid document: no file 'maindoc.xml'.");
QApplication::restoreOverrideCursor();
return false;
}
if (oasis && store->hasFile("meta.xml")) {
KoXmlDocument metaDoc;
KoOdfReadStore oasisStore(store);
if (oasisStore.loadAndParse("meta.xml", metaDoc, d->lastErrorMessage)) {
d->docInfo->loadOasis(metaDoc);
}
} else if (!oasis && store->hasFile("documentinfo.xml")) {
KoXmlDocument doc = KoXmlDocument(true);
if (oldLoadAndParse(store, "documentinfo.xml", doc)) {
d->docInfo->load(doc);
}
} else {
//kDebug( 30003 ) <<"cannot open document info";
delete d->docInfo;
d->docInfo = new KoDocumentInfo(this);
}
if (oasis && store->hasFile("VersionList.xml")) {
KNotification *notify = new KNotification("DocumentHasVersions");
notify->setText(i18n("Document %1 contains several versions. Go to File->Versions to open an old version.", store->urlOfStore().url()));
notify->addContext("url", store->urlOfStore().url());
QTimer::singleShot(0, notify, SLOT(sendEvent()));
KoXmlDocument versionInfo;
KoOdfReadStore oasisStore(store);
if (oasisStore.loadAndParse("VersionList.xml", versionInfo, d->lastErrorMessage)) {
KoXmlNode list = KoXml::namedItemNS(versionInfo, KoXmlNS::VL, "version-list");
KoXmlElement e;
forEachElement(e, list) {
if (e.localName() == "version-entry" && e.namespaceURI() == KoXmlNS::VL) {
KoVersionInfo version;
version.comment = e.attribute("comment");
version.title = e.attribute("title");
version.saved_by = e.attribute("creator");
version.date = QDateTime::fromString(e.attribute("date-time"), Qt::ISODate);
store->extractFile("Versions/" + version.title, version.data);
d->versionInfo.append(version);
}
}
}
}
bool res = completeLoading(store);
QApplication::restoreOverrideCursor();
d->isEmpty = false;
return res;
}
// For embedded documents
bool KoDocument::loadFromStore(KoStore *_store, const QString& url)
{
if (_store->open(url)) {
KoXmlDocument doc = KoXmlDocument(true);
doc.setContent(_store->device());
if (!loadXML(doc, _store)) {
_store->close();
return false;
}
_store->close();
} else {
qWarning() << "couldn't open " << url;
}
_store->pushDirectory();
// Store as document URL
if (url.startsWith(STORE_PROTOCOL)) {
setUrl(QUrl::fromUserInput(url));
} else {
setUrl(QUrl(INTERNAL_PREFIX + url));
_store->enterDirectory(url);
}
bool result = completeLoading(_store);
// Restore the "old" path
_store->popDirectory();
return result;
}
bool KoDocument::loadOasisFromStore(KoStore *store)
{
KoOdfReadStore odfStore(store);
if (! odfStore.loadAndParse(d->lastErrorMessage)) {
return false;
}
return loadOdf(odfStore);
}
bool KoDocument::addVersion(const QString& comment)
{
debugMain << "Saving the new version....";
KoStore::Backend backend = KoStore::Auto;
if (d->specialOutputFlag != 0)
return false;
QByteArray mimeType = d->outputMimeType;
QByteArray nativeOasisMime = nativeOasisMimeType();
bool oasis = !mimeType.isEmpty() && (mimeType == nativeOasisMime || mimeType == nativeOasisMime + "-template");
if (!oasis)
return false;
// TODO: use std::auto_ptr or create store on stack [needs API fixing],
// to remove all the 'delete store' in all the branches
QByteArray data;
QBuffer buffer(&data);
KoStore *store = KoStore::createStore(&buffer/*file*/, KoStore::Write, mimeType, backend);
if (store->bad()) {
delete store;
return false;
}
debugMain << "Saving to OASIS format";
KoOdfWriteStore odfStore(store);
KoXmlWriter *manifestWriter = odfStore.manifestWriter(mimeType);
Q_UNUSED(manifestWriter); // XXX why?
KoEmbeddedDocumentSaver embeddedSaver;
SavingContext documentContext(odfStore, embeddedSaver);
if (!saveOdf(documentContext)) {
debugMain << "saveOdf failed";
delete store;
return false;
}
// Save embedded objects
if (!embeddedSaver.saveEmbeddedDocuments(documentContext)) {
debugMain << "save embedded documents failed";
delete store;
return false;
}
// Write out manifest file
if (!odfStore.closeManifestWriter()) {
d->lastErrorMessage = i18n("Error while trying to write '%1'. Partition full?", QString("META-INF/manifest.xml"));
delete store;
return false;
}
if (!store->finalize()) {
delete store;
return false;
}
delete store;
KoVersionInfo version;
version.comment = comment;
version.title = "Version" + QString::number(d->versionInfo.count() + 1);
version.saved_by = documentInfo()->authorInfo("creator");
version.date = QDateTime::currentDateTime();
version.data = data;
d->versionInfo.append(version);
save(); //finally save the document + the new version
return true;
}
bool KoDocument::isStoredExtern() const
{
return !storeInternal() && hasExternURL();
}
void KoDocument::setModified()
{
d->modified = true;
}
void KoDocument::setModified(bool mod)
{
if (isAutosaving()) // ignore setModified calls due to autosaving
return;
if ( !d->readwrite && d->modified ) {
qCritical(/*1000*/) << "Can't set a read-only document to 'modified' !" << endl;
return;
}
//debugMain<<" url:" << url.path();
//debugMain<<" mod="<The document '%1' has been modified.
Do you want to save it?
", name));
switch (res) {
case KMessageBox::Yes :
save(); // NOTE: External files always in native format. ###TODO: Handle non-native format
setModified(false); // Now when queryClose() is called by closeEvent it won't do anything.
break;
case KMessageBox::No :
removeAutoSaveFiles();
setModified(false); // Now when queryClose() is called by closeEvent it won't do anything.
break;
default : // case KMessageBox::Cancel :
return res; // cancels the rest of the files
}
return res;
}
QString KoDocument::prettyPathOrUrl() const
{
QString _url(url().toDisplayString());
#ifdef Q_WS_WIN
if (url().isLocalFile()) {
_url = QDir::convertSeparators(_url);
}
#endif
return _url;
}
// Get caption from document info (title(), in about page)
QString KoDocument::caption() const
{
QString c;
if (documentInfo()) {
c = documentInfo()->aboutInfo("title");
}
const QString _url(url().fileName());
if (!c.isEmpty() && !_url.isEmpty()) {
c = QString("%1 - %2").arg(c).arg(_url);
}
else if (c.isEmpty()) {
c = _url; // Fall back to document URL
}
return c;
}
void KoDocument::setTitleModified()
{
emit titleModified(caption(), isModified());
}
bool KoDocument::completeLoading(KoStore*)
{
return true;
}
bool KoDocument::completeSaving(KoStore*)
{
return true;
}
QDomDocument KoDocument::createDomDocument(const QString& tagName, const QString& version) const
{
return createDomDocument(d->parentPart->componentData().componentName(), tagName, version);
}
//static
QDomDocument KoDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
{
QDomImplementation impl;
QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
QDomDocumentType dtype = impl.createDocumentType(tagName,
QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
url);
// The namespace URN doesn't need to include the version number.
QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
return doc;
}
QDomDocument KoDocument::saveXML()
{
errorMain << "not implemented" << endl;
d->lastErrorMessage = i18n("Internal error: saveXML not implemented");
return QDomDocument();
}
bool KoDocument::isNativeFormat(const QByteArray& mimetype) const
{
if (mimetype == nativeFormatMimeType())
return true;
return extraNativeMimeTypes().contains(mimetype);
}
int KoDocument::supportedSpecialFormats() const
{
// Apps which support special output flags can add reimplement and add to this.
// E.g. this is how did "saving in the 1.1 format".
// SaveAsDirectoryStore is a given since it's implemented by KoDocument itself.
// SaveEncrypted is implemented in KoDocument as well, if QCA2 was found.
#ifdef QCA2
return SaveAsDirectoryStore | SaveEncrypted;
#else
return SaveAsDirectoryStore;
#endif
}
void KoDocument::setErrorMessage(const QString& errMsg)
{
d->lastErrorMessage = errMsg;
}
QString KoDocument::errorMessage() const
{
return d->lastErrorMessage;
}
void KoDocument::showLoadingErrorDialog()
{
if (errorMessage().isEmpty()) {
KMessageBox::error(0, i18n("Could not open\n%1", localFilePath()));
}
else if (errorMessage() != "USER_CANCELED") {
KMessageBox::error(0, i18n("Could not open %1\nReason: %2", localFilePath(), errorMessage()));
}
}
bool KoDocument::isAutosaving() const
{
return d->autosaving;
}
bool KoDocument::isLoading() const
{
return d->isLoading;
}
void KoDocument::removeAutoSaveFiles()
{
// Eliminate any auto-save file
QString asf = autoSaveFile(localFilePath()); // the one in the current dir
if (QFile::exists(asf))
QFile::remove(asf);
asf = autoSaveFile(QString()); // and the one in $HOME
if (QFile::exists(asf))
QFile::remove(asf);
}
void KoDocument::setBackupFile(bool _b)
{
d->backupFile = _b;
}
bool KoDocument::backupFile()const
{
return d->backupFile;
}
void KoDocument::setBackupPath(const QString & _path)
{
d->backupPath = _path;
}
QString KoDocument::backupPath()const
{
return d->backupPath;
}
bool KoDocument::storeInternal() const
{
return d->storeInternal;
}
void KoDocument::setStoreInternal(bool i)
{
d->storeInternal = i;
//debugMain<<"="<storeInternal<<" doc:"<pageLayout;
}
void KoDocument::setPageLayout(const KoPageLayout &pageLayout)
{
d->pageLayout = pageLayout;
}
KoUnit KoDocument::unit() const
{
return d->unit;
}
void KoDocument::setUnit(const KoUnit &unit)
{
if (d->unit != unit) {
d->unit = unit;
emit unitChanged(unit);
}
}
void KoDocument::saveUnitOdf(KoXmlWriter *settingsWriter) const
{
settingsWriter->addConfigItem("unit", unit().symbol());
}
void KoDocument::initEmpty()
{
setEmpty();
setModified(false);
}
QList & KoDocument::versionList()
{
return d->versionInfo;
}
KUndo2Stack *KoDocument::undoStack()
{
return d->undoStack;
}
void KoDocument::addCommand(KUndo2Command *command)
{
if (command)
d->undoStack->push(command);
}
void KoDocument::beginMacro(const KUndo2MagicString & text)
{
d->undoStack->beginMacro(text);
}
void KoDocument::endMacro()
{
d->undoStack->endMacro();
}
void KoDocument::slotUndoStackIndexChanged(int idx)
{
// even if the document was already modified, call setModified to re-start autosave timer
setModified(idx != d->undoStack->cleanIndex());
}
void KoDocument::setProfileStream(QTextStream *profilestream)
{
d->profileStream = profilestream;
}
void KoDocument::setProfileReferenceTime(const QTime& referenceTime)
{
d->profileReferenceTime = referenceTime;
}
void KoDocument::clearUndoHistory()
{
d->undoStack->clear();
}
KoGridData &KoDocument::gridData()
{
return d->gridData;
}
KoGuidesData &KoDocument::guidesData()
{
return d->guidesData;
}
bool KoDocument::isEmpty() const
{
return d->isEmpty;
}
void KoDocument::setEmpty()
{
d->isEmpty = true;
}
// static
int KoDocument::defaultAutoSave()
{
return 300;
}
void KoDocument::resetURL() {
setUrl(QUrl());
setLocalFilePath(QString());
}
int KoDocument::pageCount() const {
return 1;
}
void KoDocument::setupOpenFileSubProgress() {}
KoDocumentInfoDlg *KoDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const
{
KoDocumentInfoDlg *dlg = new KoDocumentInfoDlg(parent, docInfo);
KoMainWindow *mainwin = dynamic_cast(parent);
if (mainwin) {
connect(dlg, SIGNAL(saveRequested()), mainwin, SLOT(slotFileSave()));
}
return dlg;
}
bool KoDocument::isReadWrite() const
{
return d->readwrite;
}
QUrl KoDocument::url() const
{
return d->m_url;
}
bool KoDocument::closeUrl(bool promptToSave)
{
abortLoad(); //just in case
if (promptToSave) {
if ( d->document->isReadWrite() && d->document->isModified()) {
if (!queryClose())
return false;
}
}
// Not modified => ok and delete temp file.
d->mimeType = QByteArray();
if ( d->m_bTemp )
{
QFile::remove( d->m_file );
d->m_bTemp = false;
}
// It always succeeds for a read-only part,
// but the return value exists for reimplementations
// (e.g. pressing cancel for a modified read-write part)
return true;
}
bool KoDocument::saveAs( const QUrl &kurl )
{
if (!kurl.isValid())
{
qCritical(/*1000*/) << "saveAs: Malformed URL " << kurl.url() << endl;
return false;
}
d->m_duringSaveAs = true;
d->m_originalURL = d->m_url;
d->m_originalFilePath = d->m_file;
d->m_url = kurl; // Store where to upload in saveToURL
d->prepareSaving();
bool result = save(); // Save local file and upload local file
if (!result) {
d->m_url = d->m_originalURL;
d->m_file = d->m_originalFilePath;
d->m_duringSaveAs = false;
d->m_originalURL = QUrl();
d->m_originalFilePath.clear();
}
return result;
}
bool KoDocument::save()
{
d->m_saveOk = false;
if ( d->m_file.isEmpty() ) // document was created empty
d->prepareSaving();
DocumentProgressProxy *progressProxy = 0;
if (!d->document->progressProxy()) {
KoMainWindow *mainWindow = 0;
if (d->parentPart->mainwindowCount() > 0) {
mainWindow = d->parentPart->mainWindows()[0];
}
progressProxy = new DocumentProgressProxy(mainWindow);
d->document->setProgressProxy(progressProxy);
}
d->document->setUrl(url());
// THIS IS WRONG! KoDocument::saveFile should move here, and whoever subclassed KoDocument to
// reimplement saveFile should now subclass KoPart.
bool ok = d->document->saveFile();
if (progressProxy) {
d->document->setProgressProxy(0);
delete progressProxy;
}
if (ok) {
return saveToUrl();
}
else {
emit canceled(QString());
}
return false;
}
bool KoDocument::waitSaveComplete()
{
if (!d->m_uploadJob)
return d->m_saveOk;
d->m_waitForSave = true;
d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
d->m_waitForSave = false;
return d->m_saveOk;
}
void KoDocument::abortLoad()
{
if ( d->m_statJob ) {
//kDebug(1000) << "Aborting job" << d->m_statJob;
d->m_statJob->kill();
d->m_statJob = 0;
}
if ( d->m_job ) {
//kDebug(1000) << "Aborting job" << d->m_job;
d->m_job->kill();
d->m_job = 0;
}
}
void KoDocument::setUrl(const QUrl &url)
{
d->m_url = url;
}
QString KoDocument::localFilePath() const
{
return d->m_file;
}
void KoDocument::setLocalFilePath( const QString &localFilePath )
{
d->m_file = localFilePath;
}
bool KoDocument::queryClose()
{
if ( !d->document->isReadWrite() || !d->document->isModified() )
return true;
QString docName = url().fileName();
if (docName.isEmpty()) docName = i18n( "Untitled" );
int res = KMessageBox::warningYesNoCancel( 0,
i18n( "The document \"%1\" has been modified.\n"
"Do you want to save your changes or discard them?" , docName ),
i18n( "Close Document" ), KStandardGuiItem::save(), KStandardGuiItem::discard() );
bool abortClose=false;
bool handled=false;
switch(res) {
case KMessageBox::Yes :
if (!handled)
{
if (d->m_url.isEmpty())
{
KoMainWindow *mainWindow = 0;
if (d->parentPart->mainWindows().count() > 0) {
mainWindow = d->parentPart->mainWindows()[0];
}
KoFileDialog dialog(mainWindow, KoFileDialog::SaveFile, "SaveDocument");
QUrl url = QUrl::fromLocalFile(dialog.filename());
if (url.isEmpty())
return false;
saveAs( url );
}
else
{
save();
}
} else if (abortClose) return false;
return waitSaveComplete();
case KMessageBox::No :
return true;
default : // case KMessageBox::Cancel :
return false;
}
}
bool KoDocument::saveToUrl()
{
if ( d->m_url.isLocalFile() ) {
d->document->setModified( false );
emit completed();
// if m_url is a local file there won't be a temp file -> nothing to remove
Q_ASSERT( !d->m_bTemp );
d->m_saveOk = true;
d->m_duringSaveAs = false;
d->m_originalURL = QUrl();
d->m_originalFilePath.clear();
return true; // Nothing to do
}
#ifndef Q_OS_WIN
else {
if (d->m_uploadJob) {
QFile::remove(d->m_uploadJob->srcUrl().toLocalFile());
d->m_uploadJob->kill();
d->m_uploadJob = 0;
}
QTemporaryFile *tempFile = new QTemporaryFile();
tempFile->open();
QString uploadFile = tempFile->fileName();
delete tempFile;
QUrl uploadUrl;
uploadUrl.setPath( uploadFile );
// Create hardlink
if (::link(QFile::encodeName(d->m_file), QFile::encodeName(uploadFile)) != 0) {
// Uh oh, some error happened.
return false;
}
d->m_uploadJob = KIO::file_move( uploadUrl, d->m_url, -1, KIO::Overwrite );
#ifndef QT_NO_DBUS
KJobWidgets::setWindow(d->m_uploadJob, 0);
#endif
connect( d->m_uploadJob, SIGNAL(result(KJob*)), this, SLOT(_k_slotUploadFinished(KJob*)) );
return true;
}
#else
return false;
#endif
}
bool KoDocument::openUrlInternal(const QUrl &url)
{
if ( !url.isValid() )
return false;
if (d->m_bAutoDetectedMime) {
d->mimeType = QByteArray();
d->m_bAutoDetectedMime = false;
}
QByteArray mimetype = d->mimeType;
if ( !closeUrl() )
return false;
d->mimeType = mimetype;
setUrl(url);
d->m_file.clear();
if (d->m_url.isLocalFile()) {
d->m_file = d->m_url.toLocalFile();
return d->openLocalFile();
}
else {
d->openRemoteFile();
return true;
}
}
// have to include this because of Q_PRIVATE_SLOT
#include
diff --git a/libs/main/KoDocument.h b/libs/main/KoDocument.h
index b4c442516d2..5dc8bde6e4c 100644
--- a/libs/main/KoDocument.h
+++ b/libs/main/KoDocument.h
@@ -1,801 +1,812 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis
Copyright (C) 2000-2005 David Faure
Copyright (C) 2007 Thorsten Zachmann
Copyright (C) 2010 Boudewijn Rempt
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KODOCUMENT_H
#define KODOCUMENT_H
#include
#include
#include "komain_export.h"
#include
#include
#include
class KUndo2Command;
class KoPart;
class KoStore;
class KoDocumentInfo;
class KoDocumentRdf;
class KoDocumentRdfBase;
class KoProgressUpdater;
class KoProgressProxy;
class KoDocumentInfoDlg;
class KoUnit;
class KoGridData;
class KoGuidesData;
class KoXmlWriter;
class QDomDocument;
// MSVC seems to need to know the declaration of the classes
// we pass references of in, when used by external modules
// e.g.
// when building chartshapecore.lib, the forward-declaration
// appraoch lead to unresolved externals warnings when it used
// the pagelayout functions.
// Also when building calligra_shape_formular.dll - FormulaDocument
// referenced the same two pagelayout functions incorrectly.
#if defined(_WIN32) || defined(_WIN64)
#include
#else
struct KoPageLayout;
#endif
class KoVersionInfo
{
public:
QDateTime date;
QString saved_by;
QString comment;
QString title;
QByteArray data; //the content of the compressed version
};
/**
* The %Calligra document class
*
* This class provides some functionality each %Calligra document should have.
*
* @short The %Calligra document class
*/
class KOMAIN_EXPORT KoDocument : public QObject, public KoDocumentBase
{
Q_OBJECT
Q_PROPERTY(bool backupFile READ backupFile WRITE setBackupFile)
Q_PROPERTY(int pageCount READ pageCount)
public:
/**
* Constructor.
*
* @param parent The KoPart that owns the document. XXX: should be removed!
* @param undoStack accepts the stack for the document. You can create any type of stack if you need.
* The stack objects will become owned by the document. This is used by Krita's KisDoc2. The default value for this
* parameter is a usual Qt's stack.
*/
explicit KoDocument(KoPart *parent,
KUndo2Stack *undoStack = new KUndo2Stack());
/**
* Destructor.
*
* The destructor does not delete any attached KoView objects and it does not
* delete the attached widget as returned by widget().
*/
virtual ~KoDocument();
/// XXX: Temporary!
KoPart *documentPart() const;
/**
* Reimplemented from KoParts::ReadWritePart for internal reasons
* (for the autosave functionality)
*/
virtual bool openUrl(const QUrl &url);
/**
* Opens the document given by @p url, without storing the URL
* in the KoDocument.
* Call this instead of openUrl() to implement KoMainWindow's
* File --> Import feature.
*
* @note This will call openUrl(). To differentiate this from an ordinary
* Open operation (in any reimplementation of openUrl() or openFile())
* call isImporting().
*/
bool importDocument(const QUrl &url);
/**
* Saves the document as @p url without changing the state of the
* KoDocument (URL, modified flag etc.). Call this instead of
* KoParts::ReadWritePart::saveAs() to implement KoMainWindow's
* File --> Export feature.
*
* @note This will call KoDocument::saveAs(). To differentiate this
* from an ordinary Save operation (in any reimplementation of
* saveFile()) call isExporting().
*/
bool exportDocument(const QUrl &url);
/**
* @brief Sets whether the document can be edited or is read only.
*
* This recursively applied to all child documents and
* KoView::updateReadWrite is called for every attached
* view.
*/
virtual void setReadWrite(bool readwrite = true);
/**
* To be preferred when a document exists. It is fast when calling
* it multiple times since it caches the result that readNativeFormatMimeType()
* delivers.
* This comes from the X-KDE-NativeMimeType key in the .desktop file.
*/
virtual QByteArray nativeFormatMimeType() const = 0;
/**
* Returns the OASIS OpenDocument mimetype of the document, if supported
* This comes from the X-KDE-NativeOasisMimeType key in the
* desktop file
*
* @return the oasis mimetype or, if it hasn't one, the nativeformatmimetype.
*/
virtual QByteArray nativeOasisMimeType() const = 0;
/// Checks whether a given mimetype can be handled natively.
bool isNativeFormat(const QByteArray& mimetype) const;
/// Returns a list of the mimetypes considered "native", i.e. which can
/// be saved by KoDocument without a filter, in *addition* to the main one
virtual QStringList extraNativeMimeTypes() const = 0;
/**
* Return the set of SupportedSpecialFormats that the application wants to
* offer in the "Save" file dialog.
*/
virtual int supportedSpecialFormats() const;
/**
* Returns the actual mimetype of the document
*/
QByteArray mimeType() const;
/**
* @brief Sets the mime type for the document.
*
* When choosing "save as" this is also the mime type
* selected by default.
*/
void setMimeType(const QByteArray & mimeType);
/**
* @brief Set the format in which the document should be saved.
*
* This is called on loading, and in "save as", so you shouldn't
* have to call it.
*
* @param mimeType the mime type (format) to use.
* @param specialOutputFlag is for "save as older version" etc.
*/
void setOutputMimeType(const QByteArray & mimeType, int specialOutputFlag = 0);
QByteArray outputMimeType() const;
int specialOutputFlag() const;
/**
* Returns true if this document was the result of opening a foreign
* file format and if the user hasn't yet saved the document (in any
* format).
*
* Used by KoMainWindow to warn the user when s/he lazily presses
* CTRL+S to save in the same foreign format, putting all his/her
* formatting at risk (normally an export confirmation only comes up
* with Save As).
*
* @param exporting specifies whether this is the setting for a
* File --> Export or File --> Save/Save As operation.
*/
bool confirmNonNativeSave(const bool exporting) const;
void setConfirmNonNativeSave(const bool exporting, const bool on);
/**
* @return true if saving/exporting should inhibit the option dialog
*/
bool saveInBatchMode() const;
/**
* @param batchMode if true, do not show the option dialog when saving or exporting.
*/
void setSaveInBatchMode(const bool batchMode);
/**
* Sets the error message to be shown to the user (use i18n()!)
* when loading or saving fails.
* If you asked the user about something and they chose "Cancel",
* set the message to the magic string "USER_CANCELED", to skip the error dialog.
*/
void setErrorMessage(const QString& errMsg);
/**
* Return the last error message. Usually KoDocument takes care of
* showing it; this method is mostly provided for non-interactive use.
*/
QString errorMessage() const;
/**
* Show the last error message in a message box.
* The dialog box will mention a loading problem.
* openUrl/openFile takes care of doing it, but not loadNativeFormat itself,
* so this is often called after loadNativeFormat returned false.
*/
void showLoadingErrorDialog();
/**
* @brief Generates a preview picture of the document
* @note The preview is used in the File Dialog and also to create the Thumbnail
*/
virtual QPixmap generatePreview(const QSize& size);
/**
* Paints the data itself.
* It's this method that %Calligra Parts have to implement.
*
* @param painter The painter object onto which will be drawn.
* @param rect The rect that should be used in the painter object.
*/
virtual void paintContent(QPainter &painter, const QRect &rect) = 0;
/**
* Tells the document that its title has been modified, either because
* the modified status changes (this is done by setModified() ) or
* because the URL or the document-info's title changed.
*/
void setTitleModified();
/**
* @return true if the document is empty.
*/
virtual bool isEmpty() const;
/**
* @brief Sets the document to empty.
*
* Used after loading a template
* (which is not empty, but not the user's input).
*
* @see isEmpty()
*/
virtual void setEmpty();
/**
* @brief Loads a document from a store.
*
* You should never have to reimplement.
*
* @param store The store to load from
* @param url An internal url, like tar:/1/2
*/
virtual bool loadFromStore(KoStore *store, const QString& url);
/**
* @brief Loads an OASIS document from a store.
* This is used for both the main document and embedded objects.
*/
virtual bool loadOasisFromStore(KoStore *store);
/**
* @brief Saves a sub-document to a store.
*
* You should not have to reimplement this.
*/
virtual bool saveToStore(KoStore *store, const QString& path);
/**
* Reimplement this method to load the contents of your Calligra document,
* from the XML document. This is for the pre-Oasis file format (maindoc.xml).
*/
virtual bool loadXML(const KoXmlDocument & doc, KoStore *store) = 0;
/**
* Reimplement this to save the contents of the %Calligra document into
* a QDomDocument. The framework takes care of saving it to the store.
*/
virtual QDomDocument saveXML();
/**
* Return a correctly created QDomDocument for this KoDocument,
* including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element.
* @param tagName the name of the tag for the root element
* @param version the DTD version (usually the application's version).
*/
QDomDocument createDomDocument(const QString& tagName, const QString& version) const;
/**
* Return a correctly created QDomDocument for an old (1.3-style) %Calligra document,
* including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element.
* This static method can be used e.g. by filters.
* @param appName the app's instance name, e.g. words, kspread, kpresenter etc.
* @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter.
* @param version the DTD version (usually the application's version).
*/
static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version);
/**
* The first thing to do in loadOasis is get hold of the office:body tag, then its child.
* If the child isn't the expected one, the error message can indicate what it is instead.
* This method returns a translated name for the type of document,
* e.g. i18n("Word Processing") for office:text.
*/
static QString tagNameToDocumentType(const QString& localName);
/**
* Loads a document in the native format from a given URL.
* Reimplement if your native format isn't XML.
*
* @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter
*/
virtual bool loadNativeFormat(const QString & file);
/**
* Saves the document in native format, to a given file
* You should never have to reimplement.
* Made public for writing templates.
*/
virtual bool saveNativeFormat(const QString & file);
/**
* Saves the document in native ODF format to the given store.
*/
bool saveNativeFormatODF(KoStore *store, const QByteArray &mimeType);
/**
* Saves the document in the native format to the given store.
*/
bool saveNativeFormatCalligra(KoStore *store);
/**
* Activate/deactivate/configure the autosave feature.
* @param delay in seconds, 0 to disable
*/
void setAutoSave(int delay);
/**
* Checks whether the document is currently in the process of autosaving
*/
bool isAutosaving() const;
/**
* Set whether the next openUrl call should check for an auto-saved file
* and offer to open it. This is usually true, but can be turned off
* (e.g. for the preview module). This only checks for names auto-saved
* files, unnamed auto-saved files are only checked on KoApplication startup.
*/
void setCheckAutoSaveFile(bool b);
/**
* Set whether the next openUrl call should show error message boxes in case
* of errors. This is usually the case, but e.g. not when generating thumbnail
* previews.
*/
void setAutoErrorHandlingEnabled(bool b);
/**
* Checks whether error message boxes should be shown.
*/
bool isAutoErrorHandlingEnabled() const;
/**
* Retrieve the default value for autosave in seconds.
* Called by the applications to use the correct default in their config
*/
static int defaultAutoSave();
/**
* @return the information concerning this document.
* @see KoDocumentInfo
*/
KoDocumentInfo *documentInfo() const;
/**
* @return the Rdf metadata for this document.
* This method should only be used by code that links to
* the RDF system and needs full access to the KoDocumentRdf object.
* @see KoDocumentRdf
*/
KoDocumentRdfBase *documentRdf() const;
/**
* Replace the current rdf document with the given rdf document. The existing RDF document
* will be deleted, and if RDF support is compiled out, KoDocument does not take ownership.
* Otherwise, KoDocument will own the rdf document.
*/
void setDocumentRdf(KoDocumentRdfBase *rdfDocument);
/**
* @return the object to report progress to.
* One can add more KoUpdaters to it to make the progress reporting more
* accurate. If no active progress reporter is present, 0 is returned.
**/
KoProgressUpdater *progressUpdater() const;
/**
* Set a custom progress proxy to use to report loading
* progress to.
*/
void setProgressProxy(KoProgressProxy *progressProxy);
KoProgressProxy* progressProxy() const;
/**
* Return true if url() is a real filename, false if url() is
* an internal url in the store, like "tar:/..."
*/
virtual bool isStoredExtern() const;
/**
* @return the page layout associated with this document (margins, pageSize, etc).
* Override this if you want to provide different sized pages.
*
* @see KoPageLayout
*/
virtual KoPageLayout pageLayout(int pageNumber = 0) const;
virtual void setPageLayout(const KoPageLayout &pageLayout);
/**
* Performs a cleanup of unneeded backup files
*/
void removeAutoSaveFiles();
void setBackupFile(bool _b);
bool backupFile()const;
/**
* Returns true if this document or any of its internal child documents are modified.
*/
Q_INVOKABLE bool isModified() const;
/**
* Returns true during loading (openUrl can be asynchronous)
*/
bool isLoading() const;
int queryCloseDia();
/**
* Sets the backup path of the document
*/
void setBackupPath(const QString & _path);
/**
* @return path to the backup document
*/
QString backupPath()const;
/**
* @return caption of the document
*
* Caption is of the form "[title] - [url]",
* built out of the document info (title) and pretty-printed
* document URL.
* If the title is not present, only the URL it returned.
*/
QString caption() const;
/**
* Sets the document URL to empty URL
* KParts doesn't allow this, but %Calligra apps have e.g. templates
* After using loadNativeFormat on a template, one wants
* to set the url to QUrl()
*/
void resetURL();
/**
* Set when you want an external embedded document to be stored internally
*/
void setStoreInternal(bool i);
/**
* @return true when external embedded documents are stored internally
*/
bool storeInternal() const;
bool hasExternURL() const;
/**
* @internal (public for KoMainWindow)
*/
void setMimeTypeAfterLoading(const QString& mimeType);
/**
* @return returns the number of pages in the document.
*/
virtual int pageCount() const;
/**
* Returns the unit used to display all measures/distances.
*/
KoUnit unit() const;
/**
* Sets the unit used to display all measures/distances.
*/
void setUnit(const KoUnit &unit);
/**
* Save the unit to the settings writer
*
* @param settingsWriter
*/
void saveUnitOdf(KoXmlWriter *settingsWriter) const;
QList &versionList();
bool loadNativeFormatFromStore(QByteArray &data);
/**
* Adds a new version and then saves the whole document.
* @param comment the comment for the version
* @return true on success, otherwise false
*/
bool addVersion(const QString& comment);
/// return the grid data for this document.
KoGridData &gridData();
/// returns the guides data for this document.
KoGuidesData &guidesData();
void clearUndoHistory();
/**
* Sets the modified flag on the document. This means that it has
* to be saved or not before deleting it.
*/
Q_INVOKABLE virtual void setModified(bool _mod);
/**
* Initialize an empty document using default values
*/
virtual void initEmpty();
/**
* Returns the global undo stack
*/
KUndo2Stack *undoStack();
/**
* Set the output stream to report profile information to.
*/
void setProfileStream(QTextStream *profilestream);
/**
* Set the output stream to report profile information to.
*/
void setProfileReferenceTime(const QTime& referenceTime);
+ /// If set, the document shall be saved even if it is not marked as modified.
+ /// @see setAlwaysAllowSaving()
+ bool alwaysAllowSaving() const;
+
+ /// Set alwaysAllowSaving to @p allow.
+ /// Enables applications to always allow saving even when document is not modified.
+ /// This makes it possible to save settings/context info without marking
+ /// the document as modified.
+ /// @see alwaysAllowSaving()
+ void setAlwaysAllowSaving(bool allow);
+
public Q_SLOTS:
/**
* Adds a command to the undo stack and executes it by calling the redo() function.
* @param command command to add to the undo stack
*/
virtual void addCommand(KUndo2Command *command);
/**
* Begins recording of a macro command. At the end endMacro needs to be called.
* @param text command description
*/
virtual void beginMacro(const KUndo2MagicString &text);
/**
* Ends the recording of a macro command.
*/
virtual void endMacro();
Q_SIGNALS:
/**
* This signal is emitted when the unit is changed by setUnit().
* It is common to connect views to it, in order to change the displayed units
* (e.g. in the rulers)
*/
void unitChanged(const KoUnit &unit);
/**
* Progress info while loading or saving. The value is in percents (i.e. a number between 0 and 100)
* Your KoDocument-derived class should emit the signal now and then during load/save.
* KoMainWindow will take care of displaying a progress bar automatically.
*/
void sigProgress(int value);
/**
* Emitted e.g. at the beginning of a save operation
* This is emitted by KoDocument and used by KoView to display a statusbar message
*/
void statusBarMessage(const QString& text);
/**
* Emitted e.g. at the end of a save operation
* This is emitted by KoDocument and used by KoView to clear the statusbar message
*/
void clearStatusBarMessage();
/**
* Emitted when the document is modified
*/
void modified(bool);
void titleModified(const QString &caption, bool isModified);
protected:
friend class KoPart;
/**
* Generate a name for the document.
*/
QString newObjectName();
QString autoSaveFile(const QString & path) const;
void setDisregardAutosaveFailure(bool disregardFailure);
/**
* Loads a document from KReadOnlyPart::m_file (KParts takes care of downloading
* remote documents).
* Applies a filter if necessary, and calls loadNativeFormat in any case
* You should not have to reimplement, except for very special cases.
*
* NOTE: this method also creates a new KoView instance!
*
* This method is called from the KReadOnlyPart::openUrl method.
*/
virtual bool openFile();
/**
* This method is called by @a openFile() to allow applications to setup there
* own KoProgressUpdater-subTasks which are then taken into account for the
* displayed progressbar during loading.
*/
virtual void setupOpenFileSubProgress();
/**
* Saves a document to KReadOnlyPart::m_file (KParts takes care of uploading
* remote documents)
* Applies a filter if necessary, and calls saveNativeFormat in any case
* You should not have to reimplement, except for very special cases.
*/
virtual bool saveFile();
/**
* Overload this function if you have to load additional files
* from a store. This function is called after loadXML()
* and after loadChildren() have been called.
*/
virtual bool completeLoading(KoStore *store);
/**
* If you want to write additional files to a store,
* then you must do it here.
* In the implementation, you should prepend the document
* url (using url().url()) before the filename, so that everything is kept relative
* to this document. For instance it will produce urls such as
* tar:/1/pictures/picture0.png, if the doc url is tar:/1
* But do this ONLY if the document is not stored extern (see isStoredExtern() ).
* If it is, then the pictures should be saved to tar:/pictures.
*/
virtual bool completeSaving(KoStore *store);
/** @internal */
virtual void setModified();
/**
* Returns whether or not the current openUrl() or openFile() call is
* actually an import operation (like File --> Import).
* This is for informational purposes only.
*/
bool isImporting() const;
/**
* Returns whether or not the current saveFile() call is actually an export
* operation (like File --> Export).
* If this function returns true during saveFile() and you are changing
* some sort of state, you _must_ restore it before the end of saveFile();
* otherwise, File --> Export will not work properly.
*/
bool isExporting() const;
public:
QString localFilePath() const;
void setLocalFilePath( const QString &localFilePath );
virtual KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const;
bool isReadWrite() const;
QUrl url() const;
void setUrl(const QUrl &url);
virtual bool closeUrl(bool promptToSave = true);
virtual bool saveAs( const QUrl &url );
public Q_SLOTS:
virtual bool save();
bool waitSaveComplete();
Q_SIGNALS:
void completed();
void canceled(const QString &);
private Q_SLOTS:
void slotAutoSave();
/// Called by the undo stack when undo or redo is called
void slotUndoStackIndexChanged(int idx);
protected:
bool oldLoadAndParse(KoStore *store, const QString& filename, KoXmlDocument& doc);
private:
bool saveToStream(QIODevice *dev);
QString checkImageMimeTypes(const QString &mimeType, const QUrl &url) const;
bool loadNativeFormatFromStore(const QString& file);
bool loadNativeFormatFromStoreInternal(KoStore *store);
bool savePreview(KoStore *store);
bool saveOasisPreview(KoStore *store, KoXmlWriter *manifestWriter);
QString prettyPathOrUrl() const;
bool queryClose();
bool saveToUrl();
bool openUrlInternal(const QUrl &url);
void abortLoad();
class Private;
Private *const d;
Q_PRIVATE_SLOT(d, void _k_slotJobFinished( KJob * job ))
Q_PRIVATE_SLOT(d, void _k_slotStatJobFinished(KJob*))
Q_PRIVATE_SLOT(d, void _k_slotGotMimeType(KIO::Job *job, const QString &mime))
Q_PRIVATE_SLOT(d, void _k_slotUploadFinished( KJob * job ))
};
Q_DECLARE_METATYPE(KoDocument*)
#endif
diff --git a/libs/main/KoMainWindow.cpp b/libs/main/KoMainWindow.cpp
index 64b7e3f781d..c6e3c33aef8 100644
--- a/libs/main/KoMainWindow.cpp
+++ b/libs/main/KoMainWindow.cpp
@@ -1,2164 +1,2164 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis
Copyright (C) 2000-2006 David Faure
Copyright (C) 2007, 2009 Thomas zander
Copyright (C) 2010 Benjamin Port
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoMainWindow.h"
#include "KoView.h"
#include "KoDocument.h"
#include "KoFilterManager.h"
#include "KoDocumentInfo.h"
#include "KoDocumentInfoDlg.h"
#include "KoFileDialog.h"
#include "KoVersionDialog.h"
#include "KoDockFactoryBase.h"
#include "KoDockWidgetTitleBar.h"
#include "KoPrintJob.h"
#include "KoDocumentEntry.h"
#include "KoDockerManager.h"
#include "KoPart.h"
#include
#include
#include "KoApplication.h"
#include
#include "KoResourcePaths.h"
#include "KoComponentData.h"
#include
#include
#include "calligraversion.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_KACTIVITIES
#include
#endif
// // qt includes
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "MainDebug.h"
class KoMainWindowPrivate
{
public:
KoMainWindowPrivate(const QByteArray &_nativeMimeType, const KoComponentData &componentData_, KoMainWindow *w)
: componentData(componentData_)
{
nativeMimeType = _nativeMimeType;
parent = w;
rootDocument = 0;
rootPart = 0;
partToOpen = 0;
mainWindowGuiIsBuilt = false;
forQuit = false;
activePart = 0;
activeView = 0;
firstTime = true;
progress = 0;
showDocumentInfo = 0;
saveAction = 0;
saveActionAs = 0;
printAction = 0;
printActionPreview = 0;
sendFileAction = 0;
exportPdf = 0;
closeFile = 0;
reloadFile = 0;
showFileVersions = 0;
importFile = 0;
exportFile = 0;
encryptDocument = 0;
#ifndef NDEBUG
uncompressToDir = 0;
#endif
isImporting = false;
isExporting = false;
windowSizeDirty = false;
lastExportSpecialOutputFlag = 0;
readOnly = false;
dockWidgetMenu = 0;
dockerManager = 0;
deferredClosingEvent = 0;
#ifdef HAVE_KACTIVITIES
activityResource = 0;
#endif
m_helpMenu = 0;
// PartManger
m_activeWidget = 0;
m_activePart = 0;
noCleanup = false;
}
~KoMainWindowPrivate() {
qDeleteAll(toolbarList);
}
void applyDefaultSettings(QPrinter &printer) {
QString title = rootDocument->documentInfo()->aboutInfo("title");
if (title.isEmpty()) {
title = rootDocument->url().fileName();
// strip off the native extension (I don't want foobar.kwd.ps when printing into a file)
QMimeType mime = QMimeDatabase().mimeTypeForName(rootDocument->outputMimeType());
if (mime.isValid()) {
const QString extension = mime.preferredSuffix();
if (title.endsWith(extension))
title.chop(extension.length());
}
}
if (title.isEmpty()) {
// #139905
title = i18n("%1 unsaved document (%2)", parent->componentData().componentDisplayName(),
QLocale().toString(QDate::currentDate(), QLocale::ShortFormat));
}
printer.setDocName(title);
}
QByteArray nativeMimeType;
KoMainWindow *parent;
KoDocument *rootDocument;
QList rootViews;
// PartManager
QPointer rootPart;
QPointer partToOpen;
QPointer activePart;
QPointer m_activePart;
QPointer m_registeredPart;
KoView *activeView;
QWidget *m_activeWidget;
QPointer progress;
QMutex progressMutex;
QList toolbarList;
bool mainWindowGuiIsBuilt;
bool forQuit;
bool firstTime;
bool windowSizeDirty;
bool readOnly;
QAction *showDocumentInfo;
QAction *saveAction;
QAction *saveActionAs;
QAction *printAction;
QAction *printActionPreview;
QAction *sendFileAction;
QAction *exportPdf;
QAction *closeFile;
QAction *reloadFile;
QAction *showFileVersions;
QAction *importFile;
QAction *exportFile;
QAction *encryptDocument;
#ifndef NDEBUG
QAction *uncompressToDir;
#endif
KToggleAction *toggleDockers;
KToggleAction *toggleDockerTitleBars;
KRecentFilesAction *recent;
bool isImporting;
bool isExporting;
QUrl lastExportUrl;
QByteArray lastExportedFormat;
int lastExportSpecialOutputFlag;
QMap dockWidgetsMap;
KActionMenu *dockWidgetMenu;
QMap dockWidgetVisibilityMap;
KoDockerManager *dockerManager;
QList dockWidgets;
QByteArray m_dockerStateBeforeHiding;
QCloseEvent *deferredClosingEvent;
#ifdef HAVE_KACTIVITIES
KActivities::ResourceInstance *activityResource;
#endif
KoComponentData componentData;
KHelpMenu *m_helpMenu;
bool noCleanup;
};
KoMainWindow::KoMainWindow(const QByteArray &nativeMimeType, const KoComponentData &componentData)
: KXmlGuiWindow()
, d(new KoMainWindowPrivate(nativeMimeType, componentData, this))
{
#ifdef Q_OS_MAC
setUnifiedTitleAndToolBarOnMac(true);
#endif
setStandardToolBarMenuEnabled(true);
setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North);
connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts()));
// PartManager
// End
QString doc;
const QStringList allFiles = KoResourcePaths::findAllResources("data", "calligra/calligra_shell.rc");
setXMLFile(findMostRecentXMLFile(allFiles, doc));
setLocalXMLFile(KoResourcePaths::locateLocal("data", "calligra/calligra_shell.rc"));
actionCollection()->addAction(KStandardAction::New, "file_new", this, SLOT(slotFileNew()));
actionCollection()->addAction(KStandardAction::Open, "file_open", this, SLOT(slotFileOpen()));
d->recent = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection());
connect(d->recent, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles()));
d->saveAction = actionCollection()->addAction(KStandardAction::Save, "file_save", this, SLOT(slotFileSave()));
d->saveActionAs = actionCollection()->addAction(KStandardAction::SaveAs, "file_save_as", this, SLOT(slotFileSaveAs()));
d->printAction = actionCollection()->addAction(KStandardAction::Print, "file_print", this, SLOT(slotFilePrint()));
d->printActionPreview = actionCollection()->addAction(KStandardAction::PrintPreview, "file_print_preview", this, SLOT(slotFilePrintPreview()));
d->exportPdf = new QAction(i18n("Export as PDF..."), this);
d->exportPdf->setIcon(koIcon("application-pdf"));
actionCollection()->addAction("file_export_pdf", d->exportPdf);
connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf()));
d->sendFileAction = actionCollection()->addAction(KStandardAction::Mail, "file_send_file", this, SLOT(slotEmailFile()));
d->closeFile = actionCollection()->addAction(KStandardAction::Close, "file_close", this, SLOT(slotFileClose()));
actionCollection()->addAction(KStandardAction::Quit, "file_quit", this, SLOT(slotFileQuit()));
d->reloadFile = new QAction(i18n("Reload"), this);
actionCollection()->addAction("file_reload_file", d->reloadFile);
connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile()));
d->showFileVersions = new QAction(i18n("Versions..."), this);
actionCollection()->addAction("file_versions_file", d->showFileVersions);
connect(d->showFileVersions, SIGNAL(triggered(bool)), this, SLOT(slotVersionsFile()));
d->importFile = new QAction(koIcon("document-import"), i18n("Open ex&isting Document as Untitled Document..."), this);
actionCollection()->addAction("file_import_file", d->importFile);
connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile()));
d->exportFile = new QAction(koIcon("document-export"), i18n("E&xport..."), this);
actionCollection()->addAction("file_export_file", d->exportFile);
connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile()));
d->encryptDocument = new QAction(i18n("En&crypt Document"), this);
actionCollection()->addAction("file_encrypt_doc", d->encryptDocument);
connect(d->encryptDocument, SIGNAL(triggered(bool)), this, SLOT(slotEncryptDocument()));
#ifndef NDEBUG
d->uncompressToDir = new QAction(i18n("&Uncompress to Directory"), this);
actionCollection()->addAction("file_uncompress_doc", d->uncompressToDir);
connect(d->uncompressToDir, SIGNAL(triggered(bool)), this, SLOT(slotUncompressToDir()));
#endif
QAction *actionNewView = new QAction(koIcon("window-new"), i18n("&New View"), this);
actionCollection()->addAction("view_newview", actionNewView);
connect(actionNewView, SIGNAL(triggered(bool)), this, SLOT(newView()));
/* The following entry opens the document information dialog. Since the action is named so it
intends to show data this entry should not have a trailing ellipses (...). */
d->showDocumentInfo = new QAction(koIcon("document-properties"), i18n("Document Information"), this);
actionCollection()->addAction("file_documentinfo", d->showDocumentInfo);
connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo()));
KStandardAction::keyBindings(this, SLOT(slotConfigureKeys()), actionCollection());
KStandardAction::configureToolbars(this, SLOT(slotConfigureToolbars()), actionCollection());
d->showDocumentInfo->setEnabled(false);
d->saveActionAs->setEnabled(false);
d->reloadFile->setEnabled(false);
d->showFileVersions->setEnabled(false);
d->importFile->setEnabled(true); // always enabled like File --> Open
d->exportFile->setEnabled(false);
d->saveAction->setEnabled(false);
d->printAction->setEnabled(false);
d->printActionPreview->setEnabled(false);
d->sendFileAction->setEnabled(false);
d->exportPdf->setEnabled(false);
d->closeFile->setEnabled(false);
d->encryptDocument->setEnabled(false);
#ifndef NDEBUG
d->uncompressToDir->setEnabled(false);
#endif
KToggleAction *fullscreenAction = new KToggleAction(koIcon("view-fullscreen"), i18n("Full Screen Mode"), this);
actionCollection()->addAction("view_fullscreen", fullscreenAction);
actionCollection()->setDefaultShortcut(fullscreenAction, QKeySequence::FullScreen);
connect(fullscreenAction, SIGNAL(toggled(bool)), this, SLOT(viewFullscreen(bool)));
d->toggleDockers = new KToggleAction(i18n("Show Dockers"), this);
d->toggleDockers->setChecked(true);
actionCollection()->addAction("view_toggledockers", d->toggleDockers);
connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool)));
d->toggleDockerTitleBars = new KToggleAction(i18nc("@action:inmenu", "Show Docker Titlebars"), this);
KConfigGroup configGroupInterface = KSharedConfig::openConfig()->group("Interface");
d->toggleDockerTitleBars->setChecked(configGroupInterface.readEntry("ShowDockerTitleBars", true));
d->toggleDockerTitleBars->setVisible(false);
actionCollection()->addAction("view_toggledockertitlebars", d->toggleDockerTitleBars);
connect(d->toggleDockerTitleBars, SIGNAL(toggled(bool)), SLOT(showDockerTitleBars(bool)));
d->dockWidgetMenu = new KActionMenu(i18n("Dockers"), this);
actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu);
d->dockWidgetMenu->setVisible(false);
d->dockWidgetMenu->setDelayed(false);
// Load list of recent files
KSharedConfigPtr configPtr = componentData.config();
d->recent->loadEntries(configPtr->group("RecentFiles"));
createMainwindowGUI();
d->mainWindowGuiIsBuilt = true;
// we first figure out some good default size and restore the x,y position. See bug 285804Z.
KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow");
QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray()));
if (!restoreGeometry(geom)) {
const int scnum = QApplication::desktop()->screenNumber(parentWidget());
QRect desk = QApplication::desktop()->availableGeometry(scnum);
// if the desktop is virtual then use virtual screen size
if (QApplication::desktop()->isVirtualDesktop()) {
desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen());
desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum));
}
quint32 x = desk.x();
quint32 y = desk.y();
quint32 w = 0;
quint32 h = 0;
// Default size -- maximize on small screens, something useful on big screens
const int deskWidth = desk.width();
if (deskWidth > 1024) {
// a nice width, and slightly less than total available
// height to componensate for the window decs
w = (deskWidth / 3) * 2;
h = (desk.height() / 3) * 2;
}
else {
w = desk.width();
h = desk.height();
}
x += (desk.width() - w) / 2;
y += (desk.height() - h) / 2;
move(x,y);
setGeometry(geometry().x(), geometry().y(), w, h);
}
restoreState(QByteArray::fromBase64(cfg.readEntry("ko_windowstate", QByteArray())));
d->dockerManager = new KoDockerManager(this);
}
void KoMainWindow::setNoCleanup(bool noCleanup)
{
d->noCleanup = noCleanup;
}
KoMainWindow::~KoMainWindow()
{
KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow");
cfg.writeEntry("ko_geometry", saveGeometry().toBase64());
cfg.writeEntry("ko_windowstate", saveState().toBase64());
// Explicitly delete the docker manager to ensure that it is deleted before the dockers
delete d->dockerManager;
d->dockerManager = 0;
// The doc and view might still exist (this is the case when closing the window)
if (d->rootPart)
d->rootPart->removeMainWindow(this);
if (d->partToOpen) {
d->partToOpen->removeMainWindow(this);
delete d->partToOpen;
}
// safety first ;)
setActivePart(0, 0);
if (d->rootViews.indexOf(d->activeView) == -1) {
delete d->activeView;
d->activeView = 0;
}
while (!d->rootViews.isEmpty()) {
delete d->rootViews.takeFirst();
}
if(d->noCleanup)
return;
// We have to check if this was a root document.
// This has to be checked from queryClose, too :)
if (d->rootPart && d->rootPart->viewCount() == 0) {
//debugMain <<"Destructor. No more views, deleting old doc" << d->rootDoc;
delete d->rootDocument;
}
delete d;
}
void KoMainWindow::setRootDocument(KoDocument *doc, KoPart *part, bool deletePrevious)
{
if (d->rootDocument == doc)
return;
if (d->partToOpen && d->partToOpen->document() != doc) {
d->partToOpen->removeMainWindow(this);
if (deletePrevious) delete d->partToOpen;
}
d->partToOpen = 0;
//debugMain <<"KoMainWindow::setRootDocument this =" << this <<" doc =" << doc;
QList oldRootViews = d->rootViews;
d->rootViews.clear();
KoDocument *oldRootDoc = d->rootDocument;
KoPart *oldRootPart = d->rootPart;
if (oldRootDoc) {
oldRootDoc->disconnect(this);
oldRootPart->removeMainWindow(this);
if (dockerManager()) {
dockerManager()->resetToolDockerWidgets();
}
// Hide all dockwidgets and remember their old state
d->dockWidgetVisibilityMap.clear();
foreach(QDockWidget* dockWidget, d->dockWidgetsMap) {
d->dockWidgetVisibilityMap.insert(dockWidget, dockWidget->isVisible());
dockWidget->setVisible(false);
}
d->toggleDockerTitleBars->setVisible(false);
d->dockWidgetMenu->setVisible(false);
}
d->rootDocument = doc;
// XXX remove this after the splitting
if (!part && doc) {
d->rootPart = doc->documentPart();
}
else {
d->rootPart = part;
}
if (doc) {
d->toggleDockerTitleBars->setVisible(true);
d->dockWidgetMenu->setVisible(true);
d->m_registeredPart = d->rootPart.data();
KoView *view = d->rootPart->createView(doc, this);
setCentralWidget(view);
d->rootViews.append(view);
view->show();
view->setFocus();
// The addMainWindow has been done already if using openUrl
if (!d->rootPart->mainWindows().contains(this)) {
d->rootPart->addMainWindow(this);
}
}
bool enable = d->rootDocument != 0 ? true : false;
d->showDocumentInfo->setEnabled(enable);
d->saveAction->setEnabled(enable);
d->saveActionAs->setEnabled(enable);
d->importFile->setEnabled(enable);
d->exportFile->setEnabled(enable);
d->encryptDocument->setEnabled(enable);
#ifndef NDEBUG
d->uncompressToDir->setEnabled(enable);
#endif
d->printAction->setEnabled(enable);
d->printActionPreview->setEnabled(enable);
d->sendFileAction->setEnabled(enable);
d->exportPdf->setEnabled(enable);
d->closeFile->setEnabled(enable);
updateCaption();
setActivePart(d->rootPart, doc ? d->rootViews.first() : 0);
emit restoringDone();
while(!oldRootViews.isEmpty()) {
delete oldRootViews.takeFirst();
}
if (oldRootPart && oldRootPart->viewCount() == 0) {
//debugMain <<"No more views, deleting old doc" << oldRootDoc;
oldRootDoc->clearUndoHistory();
if(deletePrevious)
delete oldRootDoc;
}
if (doc && !d->dockWidgetVisibilityMap.isEmpty()) {
foreach(QDockWidget* dockWidget, d->dockWidgetsMap) {
dockWidget->setVisible(d->dockWidgetVisibilityMap.value(dockWidget));
}
}
if (!d->rootDocument) {
statusBar()->setVisible(false);
}
else {
#ifdef Q_OS_MAC
statusBar()->setMaximumHeight(28);
#endif
connect(d->rootDocument, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified(QString,bool)));
}
}
void KoMainWindow::updateReloadFileAction(KoDocument *doc)
{
d->reloadFile->setEnabled(doc && !doc->url().isEmpty());
}
void KoMainWindow::updateVersionsFileAction(KoDocument *doc)
{
//TODO activate it just when we save it in oasis file format
d->showFileVersions->setEnabled(doc && !doc->url().isEmpty() && (doc->outputMimeType() == doc->nativeOasisMimeType() || doc->outputMimeType() == doc->nativeOasisMimeType() + "-template"));
}
void KoMainWindow::setReadWrite(bool readwrite)
{
d->saveAction->setEnabled(readwrite);
d->importFile->setEnabled(readwrite);
d->readOnly = !readwrite;
updateCaption();
}
void KoMainWindow::addRecentURL(const QUrl &url)
{
debugMain << "KoMainWindow::addRecentURL url=" << url.toDisplayString();
// Add entry to recent documents list
// (call coming from KoDocument because it must work with cmd line, template dlg, file/open, etc.)
if (!url.isEmpty()) {
bool ok = true;
if (url.isLocalFile()) {
QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile();
const QStringList tmpDirs = QStandardPaths::standardLocations(QStandardPaths::TempLocation);
foreach (const QString &tmpDir, tmpDirs) {
if (path.startsWith(tmpDir)) {
ok = false; // it's in the tmp resource
break;
}
}
if (ok) {
KRecentDocument::add(QUrl::fromLocalFile(path));
KRecentDirs::add(":OpenDialog", QFileInfo(path).dir().canonicalPath());
}
} else {
KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash));
}
if (ok) {
d->recent->addUrl(url);
}
saveRecentFiles();
#ifdef HAVE_KACTIVITIES
if (!d->activityResource) {
d->activityResource = new KActivities::ResourceInstance(winId(), this);
}
d->activityResource->setUri(url);
#endif
}
}
void KoMainWindow::saveRecentFiles()
{
// Save list of recent files
KSharedConfigPtr config = componentData().config();
debugMain << this << " Saving recent files list into config. componentData()=" << componentData().componentName();
d->recent->saveEntries(config->group("RecentFiles"));
config->sync();
// Tell all windows to reload their list, after saving
// Doesn't work multi-process, but it's a start
foreach(KMainWindow* window, KMainWindow::memberList())
static_cast(window)->reloadRecentFileList();
}
void KoMainWindow::reloadRecentFileList()
{
KSharedConfigPtr config = componentData().config();
d->recent->loadEntries(config->group("RecentFiles"));
}
KoPart* KoMainWindow::createPart() const
{
KoDocumentEntry entry = KoDocumentEntry::queryByMimeType(d->nativeMimeType);
QString errorMsg;
KoPart *part = entry.createKoPart(&errorMsg);
if (!part || !errorMsg.isEmpty()) {
return 0;
}
return part;
}
void KoMainWindow::updateCaption()
{
debugMain << "KoMainWindow::updateCaption()";
if (!d->rootDocument) {
updateCaption(QString(), false);
}
else {
QString caption( d->rootDocument->caption() );
if (d->readOnly) {
caption += ' ' + i18n("(write protected)");
}
updateCaption(caption, d->rootDocument->isModified());
if (!rootDocument()->url().fileName().isEmpty())
d->saveAction->setToolTip(i18n("Save as %1", d->rootDocument->url().fileName()));
else
d->saveAction->setToolTip(i18n("Save"));
}
}
void KoMainWindow::updateCaption(const QString & caption, bool mod)
{
debugMain << "KoMainWindow::updateCaption(" << caption << "," << mod << ")";
#ifdef CALLIGRA_ALPHA
setCaption(QString("ALPHA %1: %2").arg(CALLIGRA_ALPHA).arg(caption), mod);
return;
#endif
#ifdef CALLIGRA_BETA
setCaption(QString("BETA %1: %2").arg(CALLIGRA_BETA).arg(caption), mod);
return;
#endif
#ifdef CALLIGRA_RC
setCaption(QString("RELEASE CANDIDATE %1: %2").arg(CALLIGRA_RC).arg(caption), mod);
return;
#endif
setCaption(caption, mod);
}
KoDocument *KoMainWindow::rootDocument() const
{
return d->rootDocument;
}
KoView *KoMainWindow::rootView() const
{
if (d->rootViews.indexOf(d->activeView) != -1)
return d->activeView;
return d->rootViews.first();
}
bool KoMainWindow::openDocument(const QUrl &url)
{
if (!KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, 0)) {
KMessageBox::error(0, i18n("The file %1 does not exist.", url.url()));
d->recent->removeUrl(url); //remove the file from the recent-opened-file-list
saveRecentFiles();
return false;
}
return openDocumentInternal(url);
}
bool KoMainWindow::openDocument(KoPart *newPart, const QUrl &url)
{
// the part always has a document; the document doesn't know about the part.
KoDocument *newdoc = newPart->document();
if (!KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, 0)) {
newdoc->initEmpty(); //create an empty document
setRootDocument(newdoc, newPart);
newdoc->setUrl(url);
QMimeType mime = QMimeDatabase().mimeTypeForUrl(url);
QString mimetype = (!mime.isValid() || mime.isDefault()) ? newdoc->nativeFormatMimeType() : mime.name();
newdoc->setMimeTypeAfterLoading(mimetype);
updateCaption();
return true;
}
return openDocumentInternal(url, newPart, newdoc);
}
bool KoMainWindow::openDocumentInternal(const QUrl &url, KoPart *newpart, KoDocument *newdoc)
{
debugMain <<"KoMainWindow::openDocument" << url.url();
if (!newpart)
newpart = createPart();
if (!newpart)
return false;
if (!newdoc)
newdoc = newpart->document();
d->firstTime = true;
connect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int)));
connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString)));
newpart->addMainWindow(this); // used by openUrl
bool openRet = (!isImporting()) ? newdoc->openUrl(url) : newdoc->importDocument(url);
if (!openRet) {
newpart->removeMainWindow(this);
delete newdoc;
delete newpart;
return false;
}
updateReloadFileAction(newdoc);
updateVersionsFileAction(newdoc);
KFileItem file(url, newdoc->mimeType(), KFileItem::Unknown);
if (!file.isWritable()) {
setReadWrite(false);
}
return true;
}
// Separate from openDocument to handle async loading (remote URLs)
void KoMainWindow::slotLoadCompleted()
{
debugMain << "KoMainWindow::slotLoadCompleted";
KoDocument *newdoc = qobject_cast(sender());
KoPart *newpart = newdoc->documentPart();
if (d->rootDocument && d->rootDocument->isEmpty()) {
// Replace current empty document
setRootDocument(newdoc);
} else if (d->rootDocument && !d->rootDocument->isEmpty()) {
// Open in a new main window
// (Note : could create the main window first and the doc next for this
// particular case, that would give a better user feedback...)
KoMainWindow *s = newpart->createMainWindow();
s->show();
newpart->removeMainWindow(this);
s->setRootDocument(newdoc, newpart);
} else {
// We had no document, set the new one
setRootDocument(newdoc);
}
slotProgress(-1);
disconnect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int)));
disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString)));
emit loadCompleted();
}
void KoMainWindow::slotLoadCanceled(const QString & errMsg)
{
debugMain << "KoMainWindow::slotLoadCanceled";
if (!errMsg.isEmpty()) // empty when canceled by user
KMessageBox::error(this, errMsg);
// ... can't delete the document, it's the one who emitted the signal...
KoDocument* doc = qobject_cast(sender());
Q_ASSERT(doc);
disconnect(doc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int)));
disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString)));
}
void KoMainWindow::slotSaveCanceled(const QString &errMsg)
{
debugMain << "KoMainWindow::slotSaveCanceled";
if (!errMsg.isEmpty()) // empty when canceled by user
KMessageBox::error(this, errMsg);
slotSaveCompleted();
}
void KoMainWindow::slotSaveCompleted()
{
debugMain << "KoMainWindow::slotSaveCompleted";
KoDocument* doc = qobject_cast(sender());
Q_ASSERT(doc);
disconnect(doc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int)));
disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString)));
if (d->deferredClosingEvent) {
KXmlGuiWindow::closeEvent(d->deferredClosingEvent);
}
}
// returns true if we should save, false otherwise.
bool KoMainWindow::exportConfirmation(const QByteArray &outputFormat)
{
KConfigGroup group = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName());
if (!group.readEntry("WantExportConfirmation", true)) {
return true;
}
QMimeType mime = QMimeDatabase().mimeTypeForName(outputFormat);
QString comment = mime.isValid() ? mime.comment() : i18n("%1 (unknown file type)", QString::fromLatin1(outputFormat));
// Warn the user
int ret;
if (!isExporting()) { // File --> Save
ret = KMessageBox::warningContinueCancel
(
this,
i18n("Saving as a %1 may result in some loss of formatting."
"Do you still want to save in this format?
",
QString("%1").arg(comment)), // in case we want to remove the bold later
i18n("Confirm Save"),
KStandardGuiItem::save(),
KStandardGuiItem::cancel(),
"NonNativeSaveConfirmation"
);
} else { // File --> Export
ret = KMessageBox::warningContinueCancel
(
this,
i18n("Exporting as a %1 may result in some loss of formatting."
"Do you still want to export to this format?
",
QString("%1").arg(comment)), // in case we want to remove the bold later
i18n("Confirm Export"),
KGuiItem(i18n("Export")),
KStandardGuiItem::cancel(),
"NonNativeExportConfirmation" // different to the one used for Save (above)
);
}
return (ret == KMessageBox::Continue);
}
bool KoMainWindow::saveDocument(bool saveas, bool silent, int specialOutputFlag)
{
if (!d->rootDocument || !d->rootPart) {
return true;
}
bool reset_url;
if (d->rootDocument->url().isEmpty()) {
emit saveDialogShown();
reset_url = true;
saveas = true;
} else {
reset_url = false;
}
connect(d->rootDocument, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int)));
connect(d->rootDocument, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
connect(d->rootDocument, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString)));
QUrl oldURL = d->rootDocument->url();
QString oldFile = d->rootDocument->localFilePath();
QByteArray _native_format = d->rootDocument->nativeFormatMimeType();
QByteArray oldOutputFormat = d->rootDocument->outputMimeType();
int oldSpecialOutputFlag = d->rootDocument->specialOutputFlag();
QUrl suggestedURL = d->rootDocument->url();
QStringList mimeFilter;
QMimeType mime = QMimeDatabase().mimeTypeForName(_native_format);
if (!mime.isValid())
// QT5TODO: find if there is no better way to get an object for the default type
mime = QMimeDatabase().mimeTypeForName(QStringLiteral("application/octet-stream"));
if (specialOutputFlag)
mimeFilter = mime.globPatterns();
else
mimeFilter = KoFilterManager::mimeFilter(_native_format,
KoFilterManager::Export,
d->rootDocument->extraNativeMimeTypes());
if (!mimeFilter.contains(oldOutputFormat) && !isExporting()) {
debugMain << "KoMainWindow::saveDocument no export filter for" << oldOutputFormat;
// --- don't setOutputMimeType in case the user cancels the Save As
// dialog and then tries to just plain Save ---
// suggest a different filename extension (yes, we fortunately don't all live in a world of magic :))
QString suggestedFilename = suggestedURL.fileName();
if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name
int c = suggestedFilename.lastIndexOf('.');
const QString ext = mime.preferredSuffix();
if (!ext.isEmpty()) {
if (c < 0)
suggestedFilename += ext;
else
suggestedFilename = suggestedFilename.left(c) + ext;
} else { // current filename extension wrong anyway
if (c > 0) {
// this assumes that a . signifies an extension, not just a .
suggestedFilename = suggestedFilename.left(c);
}
}
suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename);
suggestedURL.setPath(suggestedURL.path() + suggestedFilename);
}
// force the user to choose outputMimeType
saveas = true;
}
bool ret = false;
if (d->rootDocument->url().isEmpty() || saveas) {
// if you're just File/Save As'ing to change filter options you
// don't want to be reminded about overwriting files etc.
bool justChangingFilterOptions = false;
KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveDocument");
dialog.setCaption(i18n("untitled"));
dialog.setDefaultDir((isExporting() && !d->lastExportUrl.isEmpty()) ?
d->lastExportUrl.toLocalFile() : suggestedURL.toLocalFile());
dialog.setMimeTypeFilters(mimeFilter);
QUrl newURL = QUrl::fromUserInput(dialog.filename());
if (newURL.isLocalFile()) {
QString fn = newURL.toLocalFile();
if (QFileInfo(fn).completeSuffix().isEmpty()) {
QMimeType mime = QMimeDatabase().mimeTypeForName(_native_format);
fn.append(mime.preferredSuffix());
newURL = QUrl::fromLocalFile(fn);
}
}
QByteArray outputFormat = _native_format;
if (!specialOutputFlag) {
QMimeType mime = QMimeDatabase().mimeTypeForUrl(newURL);
outputFormat = mime.name().toLatin1();
}
if (!isExporting())
justChangingFilterOptions = (newURL == d->rootDocument->url()) &&
(outputFormat == d->rootDocument->mimeType()) &&
(specialOutputFlag == oldSpecialOutputFlag);
else
justChangingFilterOptions = (newURL == d->lastExportUrl) &&
(outputFormat == d->lastExportedFormat) &&
(specialOutputFlag == d->lastExportSpecialOutputFlag);
bool bOk = true;
if (newURL.isEmpty()) {
bOk = false;
}
// adjust URL before doing checks on whether the file exists.
if (specialOutputFlag) {
QString fileName = newURL.fileName();
if ( specialOutputFlag== KoDocument::SaveAsDirectoryStore) {
// Do nothing
}
else if (specialOutputFlag == KoDocument::SaveEncrypted) {
int dot = fileName.lastIndexOf('.');
QString ext = mime.preferredSuffix();
if (!ext.isEmpty()) {
if (dot < 0) fileName += ext;
else fileName = fileName.left(dot) + ext;
} else { // current filename extension wrong anyway
if (dot > 0) fileName = fileName.left(dot);
}
newURL = newURL.adjusted(QUrl::RemoveFilename);
newURL.setPath(newURL.path() + fileName);
}
}
if (bOk) {
bool wantToSave = true;
// don't change this line unless you know what you're doing :)
if (!justChangingFilterOptions || d->rootDocument->confirmNonNativeSave(isExporting())) {
if (!d->rootDocument->isNativeFormat(outputFormat))
wantToSave = exportConfirmation(outputFormat);
}
if (wantToSave) {
//
// Note:
// If the user is stupid enough to Export to the current URL,
// we do _not_ change this operation into a Save As. Reasons
// follow:
//
// 1. A check like "isExporting() && oldURL == newURL"
// doesn't _always_ work on case-insensitive filesystems
// and inconsistent behaviour is bad.
// 2. It is probably not a good idea to change d->rootDocument->mimeType
// and friends because the next time the user File/Save's,
// (not Save As) they won't be expecting that they are
// using their File/Export settings
//
// As a bad side-effect of this, the modified flag will not
// be updated and it is possible that what is currently on
// their screen is not what is stored on disk (through loss
// of formatting). But if you are dumb enough to change
// mimetype but not the filename, then arguably, _you_ are
// the "bug" :)
//
// - Clarence
//
d->rootDocument->setOutputMimeType(outputFormat, specialOutputFlag);
if (!isExporting()) { // Save As
ret = d->rootDocument->saveAs(newURL);
if (ret) {
debugMain << "Successful Save As!";
addRecentURL(newURL);
setReadWrite(true);
} else {
debugMain << "Failed Save As!";
d->rootDocument->setUrl(oldURL);
d->rootDocument->setLocalFilePath(oldFile);
d->rootDocument->setOutputMimeType(oldOutputFormat, oldSpecialOutputFlag);
}
} else { // Export
ret = d->rootDocument->exportDocument(newURL);
if (ret) {
// a few file dialog convenience things
d->lastExportUrl = newURL;
d->lastExportedFormat = outputFormat;
d->lastExportSpecialOutputFlag = specialOutputFlag;
}
// always restore output format
d->rootDocument->setOutputMimeType(oldOutputFormat, oldSpecialOutputFlag);
}
if (silent) // don't let the document change the window caption
d->rootDocument->setTitleModified();
} // if (wantToSave) {
else
ret = false;
} // if (bOk) {
else
ret = false;
} else { // saving
bool needConfirm = d->rootDocument->confirmNonNativeSave(false) && !d->rootDocument->isNativeFormat(oldOutputFormat);
if (!needConfirm ||
(needConfirm && exportConfirmation(oldOutputFormat /* not so old :) */))
) {
// be sure d->rootDocument has the correct outputMimeType!
- if (isExporting() || d->rootDocument->isModified()) {
+ if (isExporting() || d->rootDocument->isModified() || d->rootDocument->alwaysAllowSaving()) {
ret = d->rootDocument->save();
}
if (!ret) {
debugMain << "Failed Save!";
d->rootDocument->setUrl(oldURL);
d->rootDocument->setLocalFilePath(oldFile);
}
} else
ret = false;
}
if (!ret && reset_url)
d->rootDocument->resetURL(); //clean the suggested filename as the save dialog was rejected
updateReloadFileAction(d->rootDocument);
updateCaption();
return ret;
}
void KoMainWindow::closeEvent(QCloseEvent *e)
{
if(rootDocument() && rootDocument()->isLoading()) {
e->setAccepted(false);
return;
}
if (queryClose()) {
d->deferredClosingEvent = e;
if (d->partToOpen) {
// The open pane is visible
d->partToOpen->deleteOpenPane(true);
}
if (!d->m_dockerStateBeforeHiding.isEmpty()) {
restoreState(d->m_dockerStateBeforeHiding);
}
statusBar()->setVisible(true);
menuBar()->setVisible(true);
saveWindowSettings();
if(d->noCleanup)
return;
setRootDocument(0);
if (!d->dockWidgetVisibilityMap.isEmpty()) { // re-enable dockers for persistency
foreach(QDockWidget* dockWidget, d->dockWidgetsMap)
dockWidget->setVisible(d->dockWidgetVisibilityMap.value(dockWidget));
}
} else {
e->setAccepted(false);
}
}
void KoMainWindow::saveWindowSettings()
{
KSharedConfigPtr config = componentData().config();
if (d->windowSizeDirty ) {
// Save window size into the config file of our componentData
// TODO: check if this is ever read again, seems lost over the years
debugMain << "KoMainWindow::saveWindowSettings";
KConfigGroup mainWindowConfigGroup = config->group("MainWindow");
KWindowConfig::saveWindowSize(windowHandle(), mainWindowConfigGroup);
config->sync();
d->windowSizeDirty = false;
}
if ( rootDocument() && d->rootPart) {
// Save toolbar position into the config file of the app, under the doc's component name
KConfigGroup group = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName());
//debugMain <<"KoMainWindow::closeEvent -> saveMainWindowSettings rootdoc's componentData=" << d->rootPart->componentData().componentName();
saveMainWindowSettings(group);
// Save collapsable state of dock widgets
for (QMap::const_iterator i = d->dockWidgetsMap.constBegin();
i != d->dockWidgetsMap.constEnd(); ++i) {
if (i.value()->widget()) {
KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key());
dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden());
dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool());
dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value()));
}
}
}
KSharedConfig::openConfig()->sync();
resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down
}
void KoMainWindow::resizeEvent(QResizeEvent * e)
{
d->windowSizeDirty = true;
KXmlGuiWindow::resizeEvent(e);
}
bool KoMainWindow::queryClose()
{
if (rootDocument() == 0)
return true;
//debugMain <<"KoMainWindow::queryClose() viewcount=" << rootDocument()->viewCount()
// << " mainWindowCount=" << rootDocument()->mainWindowCount() << endl;
if (!d->forQuit && d->rootPart && d->rootPart->mainwindowCount() > 1)
// there are more open, and we are closing just one, so no problem for closing
return true;
// main doc + internally stored child documents
if (d->rootDocument->isModified()) {
QString name;
if (rootDocument()->documentInfo()) {
name = rootDocument()->documentInfo()->aboutInfo("title");
}
if (name.isEmpty())
name = rootDocument()->url().fileName();
if (name.isEmpty())
name = i18n("Untitled");
int res = KMessageBox::warningYesNoCancel(this,
i18n("The document '%1' has been modified.
Do you want to save it?
", name),
QString(),
KStandardGuiItem::save(),
KStandardGuiItem::discard());
switch (res) {
case KMessageBox::Yes : {
bool isNative = (d->rootDocument->outputMimeType() == d->rootDocument->nativeFormatMimeType());
if (!saveDocument(!isNative))
return false;
break;
}
case KMessageBox::No :
rootDocument()->removeAutoSaveFiles();
rootDocument()->setModified(false); // Now when queryClose() is called by closeEvent it won't do anything.
break;
default : // case KMessageBox::Cancel :
return false;
}
}
return true;
}
// Helper method for slotFileNew and slotFileClose
void KoMainWindow::chooseNewDocument(InitDocFlags initDocFlags)
{
KoDocument* doc = rootDocument();
KoPart *newpart = createPart();
KoDocument *newdoc = newpart->document();
if (!newdoc)
return;
disconnect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int)));
if ((!doc && initDocFlags == InitDocFileNew) || (doc && !doc->isEmpty())) {
KoMainWindow *s = newpart->createMainWindow();
s->show();
newpart->addMainWindow(s);
newpart->showStartUpWidget(s, true /*Always show widget*/);
return;
}
if (doc) {
setRootDocument(0);
if(d->rootDocument)
d->rootDocument->clearUndoHistory();
delete d->rootDocument;
d->rootDocument = 0;
}
newpart->addMainWindow(this);
newpart->showStartUpWidget(this, true /*Always show widget*/);
}
void KoMainWindow::slotFileNew()
{
chooseNewDocument(InitDocFileNew);
}
void KoMainWindow::slotFileOpen()
{
QUrl url;
if (!isImporting()) {
KoFileDialog dialog(this, KoFileDialog::OpenFile, "OpenDocument");
dialog.setCaption(i18n("Open Document"));
dialog.setDefaultDir(qApp->applicationName().contains("karbon")
? QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)
: QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
dialog.setMimeTypeFilters(koApp->mimeFilter(KoFilterManager::Import));
dialog.setHideNameFilterDetailsOption();
url = QUrl::fromUserInput(dialog.filename());
} else {
KoFileDialog dialog(this, KoFileDialog::ImportFile, "OpenDocument");
dialog.setCaption(i18n("Import Document"));
dialog.setDefaultDir(qApp->applicationName().contains("karbon")
? QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)
: QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
dialog.setMimeTypeFilters(koApp->mimeFilter(KoFilterManager::Import));
dialog.setHideNameFilterDetailsOption();
url = QUrl::fromUserInput(dialog.filename());
}
if (url.isEmpty())
return;
(void) openDocument(url);
}
void KoMainWindow::slotFileOpenRecent(const QUrl & url)
{
// Create a copy, because the original QUrl in the map of recent files in
// KRecentFilesAction may get deleted.
(void) openDocument(QUrl(url));
}
void KoMainWindow::slotFileSave()
{
if (saveDocument())
emit documentSaved();
}
void KoMainWindow::slotFileSaveAs()
{
if (saveDocument(true))
emit documentSaved();
}
void KoMainWindow::slotEncryptDocument()
{
if (saveDocument(false, false, KoDocument::SaveEncrypted))
emit documentSaved();
}
void KoMainWindow::slotUncompressToDir()
{
if (saveDocument(true, false, KoDocument::SaveAsDirectoryStore))
emit documentSaved();
}
void KoMainWindow::slotDocumentInfo()
{
if (!rootDocument())
return;
KoDocumentInfo *docInfo = rootDocument()->documentInfo();
if (!docInfo)
return;
KoDocumentInfoDlg *dlg = d->rootDocument->createDocumentInfoDialog(this, docInfo);
if (dlg->exec()) {
if (dlg->isDocumentSaved()) {
rootDocument()->setModified(false);
} else {
rootDocument()->setModified(true);
}
rootDocument()->setTitleModified();
}
delete dlg;
}
void KoMainWindow::slotFileClose()
{
if (queryClose()) {
saveWindowSettings();
setRootDocument(0); // don't delete this main window when deleting the document
if(d->rootDocument)
d->rootDocument->clearUndoHistory();
delete d->rootDocument;
d->rootDocument = 0;
chooseNewDocument(InitDocFileClose);
}
}
void KoMainWindow::slotFileQuit()
{
close();
}
void KoMainWindow::slotFilePrint()
{
if (!rootView())
return;
KoPrintJob *printJob = rootView()->createPrintJob();
if (printJob == 0)
return;
d->applyDefaultSettings(printJob->printer());
QPrintDialog *printDialog = rootView()->createPrintDialog( printJob, this );
if (printDialog && printDialog->exec() == QDialog::Accepted)
printJob->startPrinting(KoPrintJob::DeleteWhenDone);
else
delete printJob;
delete printDialog;
}
void KoMainWindow::slotFilePrintPreview()
{
if (!rootView())
return;
KoPrintJob *printJob = rootView()->createPrintJob();
if (printJob == 0)
return;
/* Sets the startPrinting() slot to be blocking.
The Qt print-preview dialog requires the printing to be completely blocking
and only return when the full document has been printed.
By default the KoPrintingDialog is non-blocking and
multithreading, setting blocking to true will allow it to be used in the preview dialog */
printJob->setProperty("blocking", true);
QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this);
printJob->setParent(preview); // will take care of deleting the job
connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting()));
preview->exec();
delete preview;
}
KoPrintJob* KoMainWindow::exportToPdf(const QString &pdfFileName)
{
if (!rootView())
return 0;
KoPageLayout pageLayout;
pageLayout = rootView()->pageLayout();
return exportToPdf(pageLayout, pdfFileName);
}
KoPrintJob* KoMainWindow::exportToPdf(const KoPageLayout &_pageLayout, const QString &_pdfFileName)
{
if (!rootView())
return 0;
KoPageLayout pageLayout = _pageLayout;
QString pdfFileName = _pdfFileName;
if (pdfFileName.isEmpty()) {
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
QString defaultDir = group.readEntry("SavePdfDialog");
if (defaultDir.isEmpty())
defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QUrl startUrl = QUrl::fromLocalFile(defaultDir);
KoDocument* pDoc = rootDocument();
/** if document has a file name, take file name and replace extension with .pdf */
if (pDoc && pDoc->url().isValid()) {
startUrl = pDoc->url();
QString fileName = startUrl.fileName();
fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" );
startUrl = startUrl.adjusted(QUrl::RemoveFilename);
startUrl.setPath(startUrl.path() + fileName );
}
QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout));
layoutDlg->setWindowModality(Qt::WindowModal);
if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) {
delete layoutDlg;
return 0;
}
pageLayout = layoutDlg->pageLayout();
delete layoutDlg;
KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveDocument");
dialog.setCaption(i18n("Export as PDF"));
dialog.setDefaultDir(startUrl.toLocalFile());
dialog.setMimeTypeFilters(QStringList() << "application/pdf");
QUrl url = QUrl::fromUserInput(dialog.filename());
pdfFileName = url.toLocalFile();
if (pdfFileName.isEmpty())
return 0;
}
KoPrintJob *printJob = rootView()->createPdfPrintJob();
if (printJob == 0)
return 0;
if (isHidden()) {
printJob->setProperty("noprogressdialog", true);
}
d->applyDefaultSettings(printJob->printer());
// TODO for remote files we have to first save locally and then upload.
printJob->printer().setOutputFileName(pdfFileName);
printJob->printer().setColorMode(QPrinter::Color);
if (pageLayout.format == KoPageFormat::CustomSize) {
printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter);
} else {
printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format));
}
switch (pageLayout.orientation) {
case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break;
case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break;
}
printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter);
//before printing check if the printer can handle printing
if (!printJob->canPrint()) {
KMessageBox::error(this, i18n("Cannot export to the specified file"));
}
printJob->startPrinting(KoPrintJob::DeleteWhenDone);
return printJob;
}
void KoMainWindow::slotConfigureKeys()
{
QAction* undoAction=0;
QAction* redoAction=0;
QString oldUndoText;
QString oldRedoText;
if(currentView()) {
//The undo/redo action text is "undo" + command, replace by simple text while inside editor
undoAction = currentView()->actionCollection()->action("edit_undo");
redoAction = currentView()->actionCollection()->action("edit_redo");
oldUndoText = undoAction->text();
oldRedoText = redoAction->text();
undoAction->setText(i18n("Undo"));
redoAction->setText(i18n("Redo"));
}
guiFactory()->configureShortcuts();
if(currentView()) {
undoAction->setText(oldUndoText);
redoAction->setText(oldRedoText);
}
emit keyBindingsChanged();
}
void KoMainWindow::slotConfigureToolbars()
{
if (rootDocument()) {
KConfigGroup componentConfigGroup = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName());
saveMainWindowSettings(componentConfigGroup);
}
KEditToolBar edit(factory(), this);
connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig()));
(void) edit.exec();
}
void KoMainWindow::slotNewToolbarConfig()
{
if (rootDocument()) {
KConfigGroup componentConfigGroup = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName());
applyMainWindowSettings(componentConfigGroup);
}
KXMLGUIFactory *factory = guiFactory();
Q_UNUSED(factory);
// Check if there's an active view
if (!d->activeView)
return;
plugActionList("toolbarlist", d->toolbarList);
}
void KoMainWindow::slotToolbarToggled(bool toggle)
{
//debugMain <<"KoMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true;
// The action (sender) and the toolbar have the same name
KToolBar * bar = toolBar(sender()->objectName());
if (bar) {
if (toggle)
bar->show();
else
bar->hide();
if (rootDocument()) {
KConfigGroup componentConfigGroup = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName());
saveMainWindowSettings(componentConfigGroup);
}
} else
warnMain << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!";
}
bool KoMainWindow::toolbarIsVisible(const char *tbName)
{
QWidget *tb = toolBar(tbName);
return !tb->isHidden();
}
void KoMainWindow::showToolbar(const char * tbName, bool shown)
{
QWidget * tb = toolBar(tbName);
if (!tb) {
warnMain << "KoMainWindow: toolbar " << tbName << " not found.";
return;
}
if (shown)
tb->show();
else
tb->hide();
// Update the action appropriately
foreach(QAction* action, d->toolbarList) {
if (action->objectName() != tbName) {
//debugMain <<"KoMainWindow::showToolbar setChecked" << shown;
static_cast(action)->setChecked(shown);
break;
}
}
}
void KoMainWindow::viewFullscreen(bool fullScreen)
{
if (fullScreen) {
window()->setWindowState(window()->windowState() | Qt::WindowFullScreen); // set
} else {
window()->setWindowState(window()->windowState() & ~Qt::WindowFullScreen); // reset
}
}
void KoMainWindow::slotProgress(int value)
{
QMutexLocker locker(&d->progressMutex);
debugMain << "KoMainWindow::slotProgress" << value;
if (value <= -1 || value >= 100) {
if (d->progress) {
statusBar()->removeWidget(d->progress);
delete d->progress;
d->progress = 0;
}
d->firstTime = true;
return;
}
if (d->firstTime || !d->progress) {
// The statusbar might not even be created yet.
// So check for that first, and create it if necessary
QStatusBar *bar = findChild();
if (!bar) {
statusBar()->show();
QApplication::sendPostedEvents(this, QEvent::ChildAdded);
}
if (d->progress) {
statusBar()->removeWidget(d->progress);
delete d->progress;
d->progress = 0;
}
d->progress = new QProgressBar(statusBar());
d->progress->setMaximumHeight(statusBar()->fontMetrics().height());
d->progress->setRange(0, 100);
statusBar()->addPermanentWidget(d->progress);
d->progress->show();
d->firstTime = false;
}
if (!d->progress.isNull()) {
d->progress->setValue(value);
}
qApp->processEvents();
}
void KoMainWindow::setMaxRecentItems(uint _number)
{
d->recent->setMaxItems(_number);
}
void KoMainWindow::slotEmailFile()
{
if (!rootDocument())
return;
// Subject = Document file name
// Attachment = The current file
// Message Body = The current document in HTML export? <-- This may be an option.
QString theSubject;
QStringList urls;
QString fileURL;
if (rootDocument()->url().isEmpty() ||
rootDocument()->isModified()) {
//Save the file as a temporary file
bool const tmp_modified = rootDocument()->isModified();
QUrl const tmp_url = rootDocument()->url();
QByteArray const tmp_mimetype = rootDocument()->outputMimeType();
// a little open, close, delete dance to make sure we have a nice filename
// to use, but won't block windows from creating a new file with this name.
QTemporaryFile *tmpfile = new QTemporaryFile();
tmpfile->open();
QString fileName = tmpfile->fileName();
tmpfile->close();
delete tmpfile;
QUrl u = QUrl::fromLocalFile(fileName);
rootDocument()->setUrl(u);
rootDocument()->setModified(true);
rootDocument()->setOutputMimeType(rootDocument()->nativeFormatMimeType());
saveDocument(false, true);
fileURL = fileName;
theSubject = i18n("Document");
urls.append(fileURL);
rootDocument()->setUrl(tmp_url);
rootDocument()->setModified(tmp_modified);
rootDocument()->setOutputMimeType(tmp_mimetype);
} else {
fileURL = rootDocument()->url().url();
theSubject = i18n("Document - %1", rootDocument()->url().fileName());
urls.append(fileURL);
}
debugMain << "(" << fileURL << ")";
if (!fileURL.isEmpty()) {
KToolInvocation::invokeMailer(QString(), QString(), QString(), theSubject,
QString(), //body
QString(),
urls); // attachments
}
}
void KoMainWindow::slotVersionsFile()
{
if (!rootDocument())
return;
KoVersionDialog *dlg = new KoVersionDialog(this, rootDocument());
dlg->exec();
delete dlg;
}
void KoMainWindow::slotReloadFile()
{
KoDocument* pDoc = rootDocument();
if (!pDoc || pDoc->url().isEmpty() || !pDoc->isModified())
return;
bool bOk = KMessageBox::questionYesNo(this,
i18n("You will lose all changes made since your last save\n"
"Do you want to continue?"),
i18n("Warning")) == KMessageBox::Yes;
if (!bOk)
return;
QUrl url = pDoc->url();
if (!pDoc->isEmpty()) {
saveWindowSettings();
setRootDocument(0); // don't delete this main window when deleting the document
if(d->rootDocument)
d->rootDocument->clearUndoHistory();
delete d->rootDocument;
d->rootDocument = 0;
}
openDocument(url);
return;
}
void KoMainWindow::slotImportFile()
{
debugMain << "slotImportFile()";
d->isImporting = true;
slotFileOpen();
d->isImporting = false;
}
void KoMainWindow::slotExportFile()
{
debugMain << "slotExportFile()";
d->isExporting = true;
slotFileSaveAs();
d->isExporting = false;
}
bool KoMainWindow::isImporting() const
{
return d->isImporting;
}
bool KoMainWindow::isExporting() const
{
return d->isExporting;
}
void KoMainWindow::setPartToOpen(KoPart *part)
{
d->partToOpen = part;
}
KoComponentData KoMainWindow::componentData() const
{
return d->componentData;
}
QDockWidget* KoMainWindow::createDockWidget(KoDockFactoryBase* factory)
{
QDockWidget* dockWidget = 0;
if (!d->dockWidgetsMap.contains(factory->id())) {
dockWidget = factory->createDockWidget();
// It is quite possible that a dock factory cannot create the dock; don't
// do anything in that case.
if (!dockWidget) return 0;
d->dockWidgets.push_back(dockWidget);
KoDockWidgetTitleBar *titleBar = 0;
// Check if the dock widget is supposed to be collapsable
if (!dockWidget->titleBarWidget()) {
titleBar = new KoDockWidgetTitleBar(dockWidget);
dockWidget->setTitleBarWidget(titleBar);
titleBar->setCollapsable(factory->isCollapsable());
}
dockWidget->setObjectName(factory->id());
dockWidget->setParent(this);
if (dockWidget->widget() && dockWidget->widget()->layout())
dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1);
Qt::DockWidgetArea side = Qt::RightDockWidgetArea;
bool visible = true;
switch (factory->defaultDockPosition()) {
case KoDockFactoryBase::DockTornOff:
dockWidget->setFloating(true); // position nicely?
break;
case KoDockFactoryBase::DockTop:
side = Qt::TopDockWidgetArea; break;
case KoDockFactoryBase::DockLeft:
side = Qt::LeftDockWidgetArea; break;
case KoDockFactoryBase::DockBottom:
side = Qt::BottomDockWidgetArea; break;
case KoDockFactoryBase::DockRight:
side = Qt::RightDockWidgetArea; break;
case KoDockFactoryBase::DockMinimized:
default:
side = Qt::RightDockWidgetArea;
visible = false;
}
if (rootDocument()) {
KConfigGroup group = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()).group("DockWidget " + factory->id());
side = static_cast(group.readEntry("DockArea", static_cast(side)));
if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea;
}
addDockWidget(side, dockWidget);
if (dockWidget->features() & QDockWidget::DockWidgetClosable) {
d->dockWidgetMenu->addAction(dockWidget->toggleViewAction());
if (!visible)
dockWidget->hide();
}
bool collapsed = factory->defaultCollapsed();
bool locked = false;
if (rootDocument()) {
KConfigGroup group = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()).group("DockWidget " + factory->id());
collapsed = group.readEntry("Collapsed", collapsed);
locked = group.readEntry("Locked", locked);
}
if (titleBar && collapsed)
titleBar->setCollapsed(true);
if (titleBar && locked)
titleBar->setLocked(true);
if (titleBar) {
KConfigGroup configGroupInterface = KSharedConfig::openConfig()->group("Interface");
titleBar->setVisible(configGroupInterface.readEntry("ShowDockerTitleBars", true));
}
d->dockWidgetsMap.insert(factory->id(), dockWidget);
} else {
dockWidget = d->dockWidgetsMap[ factory->id()];
}
#ifdef Q_OS_MAC
dockWidget->setAttribute(Qt::WA_MacSmallSize, true);
#endif
dockWidget->setFont(KoDockRegistry::dockFont());
connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts()));
return dockWidget;
}
void KoMainWindow::forceDockTabFonts()
{
QObjectList chis = children();
for (int i = 0; i < chis.size(); ++i) {
if (chis.at(i)->inherits("QTabBar")) {
((QTabBar *)chis.at(i))->setFont(KoDockRegistry::dockFont());
}
}
}
QList KoMainWindow::dockWidgets() const
{
return d->dockWidgetsMap.values();
}
QList KoMainWindow::canvasObservers() const
{
QList observers;
foreach(QDockWidget *docker, dockWidgets()) {
KoCanvasObserverBase *observer = dynamic_cast(docker);
if (observer) {
observers << observer;
}
}
return observers;
}
KoDockerManager * KoMainWindow::dockerManager() const
{
return d->dockerManager;
}
void KoMainWindow::toggleDockersVisibility(bool visible)
{
if (!visible) {
d->m_dockerStateBeforeHiding = saveState();
foreach(QObject* widget, children()) {
if (widget->inherits("QDockWidget")) {
QDockWidget* dw = static_cast(widget);
if (dw->isVisible()) {
dw->hide();
}
}
}
}
else {
restoreState(d->m_dockerStateBeforeHiding);
}
}
KRecentFilesAction *KoMainWindow::recentAction() const
{
return d->recent;
}
KoView* KoMainWindow::currentView() const
{
// XXX
if (d->activeView) {
return d->activeView;
}
else if (!d->rootViews.isEmpty()) {
return d->rootViews.first();
}
return 0;
}
void KoMainWindow::newView()
{
Q_ASSERT((d != 0 && d->activeView && d->activePart && d->activeView->koDocument()));
KoMainWindow *mainWindow = d->activePart->createMainWindow();
mainWindow->setRootDocument(d->activeView->koDocument(), d->activePart);
mainWindow->show();
}
void KoMainWindow::createMainwindowGUI()
{
if ( isHelpMenuEnabled() && !d->m_helpMenu ) {
d->m_helpMenu = new KHelpMenu( this, componentData().aboutData(), true );
KActionCollection *actions = actionCollection();
QAction *helpContentsAction = d->m_helpMenu->action(KHelpMenu::menuHelpContents);
QAction *whatsThisAction = d->m_helpMenu->action(KHelpMenu::menuWhatsThis);
QAction *reportBugAction = d->m_helpMenu->action(KHelpMenu::menuReportBug);
QAction *switchLanguageAction = d->m_helpMenu->action(KHelpMenu::menuSwitchLanguage);
QAction *aboutAppAction = d->m_helpMenu->action(KHelpMenu::menuAboutApp);
QAction *aboutKdeAction = d->m_helpMenu->action(KHelpMenu::menuAboutKDE);
if (helpContentsAction) {
actions->addAction(helpContentsAction->objectName(), helpContentsAction);
}
if (whatsThisAction) {
actions->addAction(whatsThisAction->objectName(), whatsThisAction);
}
if (reportBugAction) {
actions->addAction(reportBugAction->objectName(), reportBugAction);
}
if (switchLanguageAction) {
actions->addAction(switchLanguageAction->objectName(), switchLanguageAction);
}
if (aboutAppAction) {
actions->addAction(aboutAppAction->objectName(), aboutAppAction);
}
if (aboutKdeAction) {
actions->addAction(aboutKdeAction->objectName(), aboutKdeAction);
}
}
QString f = xmlFile();
setXMLFile( QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("ui/ui_standards.rc")) );
if ( !f.isEmpty() )
setXMLFile( f, true );
else
{
QString auto_file( componentData().componentName() + "ui.rc" );
setXMLFile( auto_file, true );
}
guiFactory()->addClient( this );
}
// PartManager
void KoMainWindow::removePart( KoPart *part )
{
if (d->m_registeredPart.data() != part) {
return;
}
d->m_registeredPart = 0;
if ( part == d->m_activePart ) {
setActivePart(0, 0);
}
}
void KoMainWindow::setActivePart(KoPart *part, QWidget *widget )
{
if (part && d->m_registeredPart.data() != part) {
warnMain << "trying to activate a non-registered part!" << part->objectName();
return; // don't allow someone call setActivePart with a part we don't know about
}
// don't activate twice
if ( d->m_activePart && part && d->m_activePart == part &&
(!widget || d->m_activeWidget == widget) )
return;
KoPart *oldActivePart = d->m_activePart;
QWidget *oldActiveWidget = d->m_activeWidget;
d->m_activePart = part;
d->m_activeWidget = widget;
if (oldActivePart) {
KoPart *savedActivePart = part;
QWidget *savedActiveWidget = widget;
if ( oldActiveWidget ) {
disconnect( oldActiveWidget, SIGNAL(destroyed()), this, SLOT(slotWidgetDestroyed()) );
}
d->m_activePart = savedActivePart;
d->m_activeWidget = savedActiveWidget;
}
if (d->m_activePart && d->m_activeWidget ) {
connect( d->m_activeWidget, SIGNAL(destroyed()), this, SLOT(slotWidgetDestroyed()) );
}
// Set the new active instance in KGlobal
// KGlobal::setActiveComponent(d->m_activePart ? d->m_activePart->componentData() : KGlobal::mainComponent());
// old slot called from part manager
KoPart *newPart = static_cast(d->m_activePart.data());
if (d->activePart && d->activePart == newPart) {
//debugMain <<"no need to change the GUI";
return;
}
KXMLGUIFactory *factory = guiFactory();
if (d->activeView) {
factory->removeClient(d->activeView);
unplugActionList("toolbarlist");
qDeleteAll(d->toolbarList);
d->toolbarList.clear();
}
if (!d->mainWindowGuiIsBuilt) {
createMainwindowGUI();
}
if (newPart && d->m_activeWidget && d->m_activeWidget->inherits("KoView")) {
d->activeView = qobject_cast(d->m_activeWidget);
d->activeView->actionCollection()->addAction("view_newview", actionCollection()->action("view_newview"));
d->activePart = newPart;
//debugMain <<"new active part is" << d->activePart;
factory->addClient(d->activeView);
// Position and show toolbars according to user's preference
setAutoSaveSettings(newPart->componentData().componentName(), false);
KConfigGroup configGroupInterface = KSharedConfig::openConfig()->group("Interface");
const bool showDockerTitleBar = configGroupInterface.readEntry("ShowDockerTitleBars", true);
foreach (QDockWidget *wdg, d->dockWidgets) {
if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) {
if (wdg->titleBarWidget()) {
wdg->titleBarWidget()->setVisible(showDockerTitleBar);
}
wdg->setVisible(true);
}
}
// Create and plug toolbar list for Settings menu
foreach(QWidget* it, factory->containers("ToolBar")) {
KToolBar * toolBar = ::qobject_cast(it);
if (toolBar) {
KToggleAction * act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this);
actionCollection()->addAction(toolBar->objectName().toUtf8(), act);
act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle())));
connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool)));
act->setChecked(!toolBar->isHidden());
d->toolbarList.append(act);
} else
warnMain << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!";
}
plugActionList("toolbarlist", d->toolbarList);
}
else {
d->activeView = 0;
d->activePart = 0;
}
if (d->activeView) {
d->activeView->guiActivateEvent(true);
}
}
void KoMainWindow::slotWidgetDestroyed()
{
debugMain;
if ( static_cast( sender() ) == d->m_activeWidget )
setActivePart(0, 0); //do not remove the part because if the part's widget dies, then the
//part will delete itself anyway, invoking removePart() in its destructor
}
void KoMainWindow::slotDocumentTitleModified(const QString &caption, bool mod)
{
updateCaption(caption, mod);
updateReloadFileAction(d->rootDocument);
updateVersionsFileAction(d->rootDocument);
}
void KoMainWindow::showDockerTitleBars(bool show)
{
foreach (QDockWidget *dock, dockWidgets()) {
if (dock->titleBarWidget()) {
dock->titleBarWidget()->setVisible(show);
}
}
KConfigGroup configGroupInterface = KSharedConfig::openConfig()->group("Interface");
configGroupInterface.writeEntry("ShowDockerTitleBars", show);
}
diff --git a/plan/kptmaindocument.cpp b/plan/kptmaindocument.cpp
index e5362e76309..dc4f9056263 100644
--- a/plan/kptmaindocument.cpp
+++ b/plan/kptmaindocument.cpp
@@ -1,1409 +1,1454 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999, 2000 Torben Weis
Copyright (C) 2004, 2010, 2012 Dag Andersen
Copyright (C) 2006 Raphael Langerhorst
Copyright (C) 2007 Thorsten Zachmann
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kptmaindocument.h"
#include "kptpart.h"
#include "kptview.h"
#include "kptfactory.h"
#include "kptproject.h"
#include "kptlocale.h"
#include "kptresource.h"
#include "kptcontext.h"
#include "kptschedulerpluginloader.h"
#include "kptschedulerplugin.h"
#include "kptbuiltinschedulerplugin.h"
#include "kptcommand.h"
#include "calligraplansettings.h"
#include "kpttask.h"
#include "KPlatoXmlLoader.h"
#include "kptpackage.h"
#include "kptworkpackagemergedialog.h"
#include "kptdebug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
#ifdef HAVE_KHOLIDAYS
#include
#endif
namespace KPlato
{
MainDocument::MainDocument(KoPart *part)
: KoDocument(part),
m_project( 0 ),
m_context( 0 ), m_xmlLoader(),
m_loadingTemplate( false ),
m_loadingSharedResourcesTemplate( false ),
m_viewlistModified( false ),
m_checkingForWorkPackages( false )
{
Q_ASSERT(part);
+ setAlwaysAllowSaving(true);
m_config.setReadWrite( true );
loadSchedulerPlugins();
setProject( new Project( m_config ) ); // after config & plugins are loaded
m_project->setId( m_project->uniqueNodeId() );
m_project->registerNodeId( m_project ); // register myself
connect(this, &MainDocument::insertSharedProject, this, &MainDocument::slotInsertSharedProject);
QTimer::singleShot ( 5000, this, SLOT(autoCheckForWorkPackages()) );
}
MainDocument::~MainDocument()
{
qDeleteAll( m_schedulerPlugins );
if ( m_project ) {
m_project->deref(); // deletes if last user
}
qDeleteAll( m_mergedPackages );
delete m_context;
}
void MainDocument::setReadWrite( bool rw )
{
m_config.setReadWrite( rw );
KoDocument::setReadWrite( rw );
}
void MainDocument::loadSchedulerPlugins()
{
// Add built-in scheduler
addSchedulerPlugin( "Built-in", new BuiltinSchedulerPlugin( this ) );
// Add all real scheduler plugins
SchedulerPluginLoader *loader = new SchedulerPluginLoader(this);
connect(loader, SIGNAL(pluginLoaded(QString,SchedulerPlugin*)), this, SLOT(addSchedulerPlugin(QString,SchedulerPlugin*)));
loader->loadAllPlugins();
}
void MainDocument::addSchedulerPlugin( const QString &key, SchedulerPlugin *plugin)
{
debugPlan<setConfig( m_config );
}
void MainDocument::setProject( Project *project )
{
if ( m_project ) {
disconnect( m_project, SIGNAL(projectChanged()), this, SIGNAL(changed()) );
delete m_project;
}
m_project = project;
if ( m_project ) {
connect( m_project, SIGNAL(projectChanged()), this, SIGNAL(changed()) );
// m_project->setConfig( config() );
m_project->setSchedulerPlugins( m_schedulerPlugins );
}
m_aboutPage.setProject( project );
emit changed();
}
bool MainDocument::loadOdf( KoOdfReadStore &odfStore )
{
warnPlan<< "OpenDocument not supported, let's try native xml format";
return loadXML( odfStore.contentDoc(), 0 ); // We have only one format, so try to load that!
}
bool MainDocument::loadXML( const KoXmlDocument &document, KoStore* )
{
QPointer updater;
if (progressUpdater()) {
updater = progressUpdater()->startSubtask(1, "Plan::Part::loadXML");
updater->setProgress(0);
m_xmlLoader.setUpdater( updater );
}
QString value;
KoXmlElement plan = document.documentElement();
// Check if this is the right app
value = plan.attribute( "mime", QString() );
if ( value.isEmpty() ) {
errorPlan << "No mime type specified!";
setErrorMessage( i18n( "Invalid document. No mimetype specified." ) );
return false;
}
if ( value == "application/x-vnd.kde.kplato" ) {
if (updater) {
updater->setProgress(5);
}
m_xmlLoader.setMimetype( value );
QString message;
Project *newProject = new Project( m_config );
KPlatoXmlLoader loader( m_xmlLoader, newProject );
bool ok = loader.load( plan );
if ( ok ) {
setProject( newProject );
setModified( false );
debugPlan<schedules();
// Cleanup after possible bug:
// There should *not* be any deleted schedules (or with parent == 0)
foreach ( Node *n, newProject->nodeDict()) {
foreach ( Schedule *s, n->schedules()) {
if ( s->isDeleted() ) { // true also if parent == 0
errorPlan<name()<takeSchedule( s );
delete s;
}
}
}
} else {
setErrorMessage( loader.errorMessage() );
delete newProject;
}
if (updater) {
updater->setProgress(100); // the rest is only processing, not loading
}
emit changed();
return ok;
}
if ( value != "application/x-vnd.kde.plan" ) {
errorPlan << "Unknown mime type " << value;
setErrorMessage( i18n( "Invalid document. Expected mimetype application/x-vnd.kde.plan, got %1", value ) );
return false;
}
QString syntaxVersion = plan.attribute( "version", PLAN_FILE_SYNTAX_VERSION );
m_xmlLoader.setVersion( syntaxVersion );
if ( syntaxVersion > PLAN_FILE_SYNTAX_VERSION ) {
KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel(
0, i18n( "This document was created with a newer version of Plan (syntax version: %1)\n"
"Opening it in this version of Plan will lose some information.", syntaxVersion ),
i18n( "File-Format Mismatch" ), KGuiItem( i18n( "Continue" ) ) );
if ( ret == KMessageBox::Cancel ) {
setErrorMessage( "USER_CANCELED" );
return false;
}
}
if (updater) updater->setProgress(5);
/*
#ifdef KOXML_USE_QDOM
int numNodes = plan.childNodes().count();
#else
int numNodes = plan.childNodesCount();
#endif
*/
#if 0
This test does not work any longer. KoXml adds a couple of elements not present in the file!!
if ( numNodes > 2 ) {
//TODO: Make a proper bitching about this
debugPlan <<"*** Error ***";
debugPlan <<" Children count should be maximum 2, but is" << numNodes;
return false;
}
#endif
m_xmlLoader.startLoad();
KoXmlNode n = plan.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "project" ) {
Project *newProject = new Project( m_config );
m_xmlLoader.setProject( newProject );
if ( newProject->load( e, m_xmlLoader ) ) {
if ( newProject->id().isEmpty() ) {
newProject->setId( newProject->uniqueNodeId() );
newProject->registerNodeId( newProject );
}
// The load went fine. Throw out the old project
setProject( newProject );
// Cleanup after possible bug:
// There should *not* be any deleted schedules (or with parent == 0)
foreach ( Node *n, newProject->nodeDict()) {
foreach ( Schedule *s, n->schedules()) {
if ( s->isDeleted() ) { // true also if parent == 0
errorPlan<name()<takeSchedule( s );
delete s;
}
}
}
} else {
delete newProject;
m_xmlLoader.addMsg( XMLLoaderObject::Errors, "Loading of project failed" );
//TODO add some ui here
}
}
}
m_xmlLoader.stopLoad();
if (updater) updater->setProgress(100); // the rest is only processing, not loading
setModified( false );
emit changed();
return true;
}
QDomDocument MainDocument::saveXML()
{
debugPlan;
QDomDocument document( "plan" );
document.appendChild( document.createProcessingInstruction(
"xml",
"version=\"1.0\" encoding=\"UTF-8\"" ) );
QDomElement doc = document.createElement( "plan" );
doc.setAttribute( "editor", "Plan" );
doc.setAttribute( "mime", "application/x-vnd.kde.plan" );
doc.setAttribute( "version", PLAN_FILE_SYNTAX_VERSION );
document.appendChild( doc );
// Save the project
m_project->save( doc );
return document;
}
QDomDocument MainDocument::saveWorkPackageXML( const Node *node, long id, Resource *resource )
{
debugPlan;
QDomDocument document( "plan" );
document.appendChild( document.createProcessingInstruction(
"xml",
"version=\"1.0\" encoding=\"UTF-8\"" ) );
QDomElement doc = document.createElement( "planwork" );
doc.setAttribute( "editor", "Plan" );
doc.setAttribute( "mime", "application/x-vnd.kde.plan.work" );
doc.setAttribute( "version", PLANWORK_FILE_SYNTAX_VERSION );
doc.setAttribute( "plan-version", PLAN_FILE_SYNTAX_VERSION );
document.appendChild( doc );
// Work package info
QDomElement wp = document.createElement( "workpackage" );
if ( resource ) {
wp.setAttribute( "owner", resource->name() );
wp.setAttribute( "owner-id", resource->id() );
}
wp.setAttribute( "time-tag", QDateTime::currentDateTime().toString( Qt::ISODate ) );
doc.appendChild( wp );
// Save the project
m_project->saveWorkPackageXML( doc, node, id );
return document;
}
bool MainDocument::saveWorkPackageToStream( QIODevice *dev, const Node *node, long id, Resource *resource )
{
QDomDocument doc = saveWorkPackageXML( node, id, resource );
// Save to buffer
QByteArray s = doc.toByteArray(); // utf8 already
dev->open( QIODevice::WriteOnly );
int nwritten = dev->write( s.data(), s.size() );
if ( nwritten != (int)s.size() ) {
warnPlan<<"wrote:"<m_specialOutputFlag == SaveEncrypted ) {
backend = KoStore::Encrypted;
debugPlan <<"Saving using encrypted backend.";
}*/
#endif
QByteArray mimeType = "application/x-vnd.kde.plan.work";
debugPlan <<"MimeType=" << mimeType;
KoStore *store = KoStore::createStore( file, KoStore::Write, mimeType, backend );
/* if ( d->m_specialOutputFlag == SaveEncrypted && !d->m_password.isNull( ) ) {
store->setPassword( d->m_password );
}*/
if ( store->bad() ) {
setErrorMessage( i18n( "Could not create the workpackage file for saving: %1", file ) ); // more details needed?
delete store;
return false;
}
// Tell KoStore not to touch the file names
if ( ! store->open( "root" ) ) {
setErrorMessage( i18n( "Not able to write '%1'. Partition full?", QString( "maindoc.xml") ) );
delete store;
return false;
}
KoStoreDevice dev( store );
if ( !saveWorkPackageToStream( &dev, node, id, resource ) || !store->close() ) {
debugPlan <<"saveToStream failed";
delete store;
return false;
}
node->documents().saveToStore( store );
debugPlan <<"Saving done of url:" << file;
if ( !store->finalize() ) {
delete store;
return false;
}
// Success
delete store;
return true;
}
bool MainDocument::saveWorkPackageUrl( const QUrl &_url, const Node *node, long id, Resource *resource )
{
//debugPlan<<_url;
QApplication::setOverrideCursor( Qt::WaitCursor );
emit statusBarMessage( i18n("Saving...") );
bool ret = false;
ret = saveWorkPackageFormat( _url.path(), node, id, resource ); // kzip don't handle file://
QApplication::restoreOverrideCursor();
emit clearStatusBarMessage();
return ret;
}
bool MainDocument::loadWorkPackage( Project &project, const QUrl &url )
{
debugPlan<bad() ) {
// d->lastErrorMessage = i18n( "Not a valid Calligra file: %1", file );
debugPlan<<"bad store"<open( "root" ) ) { // "old" file format (maindoc.xml)
// i18n( "File does not have a maindoc.xml: %1", file );
debugPlan<<"No root"<device(), &errorMsg, &errorLine, &errorColumn );
if ( ! ok ) {
errorPlan << "Parsing error in " << url.url() << "! Aborting!" << endl
<< " In line: " << errorLine << ", column: " << errorColumn << endl
<< " Error message: " << errorMsg;
//d->lastErrorMessage = i18n( "Parsing error in %1 at line %2, column %3\nError message: %4",filename ,errorLine, errorColumn , QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0, QCoreApplication::UnicodeUTF8));
} else {
package = loadWorkPackageXML( project, store->device(), doc, url );
if ( package ) {
package->url = url;
m_workpackages.insert( package->timeTag, package );
} else {
ok = false;
}
}
store->close();
//###
if ( ok && package && package->settings.documents ) {
ok = extractFiles( store, package );
}
delete store;
if ( ! ok ) {
// QApplication::restoreOverrideCursor();
return false;
}
return true;
}
Package *MainDocument::loadWorkPackageXML( Project &project, QIODevice *, const KoXmlDocument &document, const QUrl &/*url*/ )
{
QString value;
bool ok = true;
Project *proj = 0;
Package *package = 0;
KoXmlElement plan = document.documentElement();
// Check if this is the right app
value = plan.attribute( "mime", QString() );
if ( value.isEmpty() ) {
debugPlan << "No mime type specified!";
setErrorMessage( i18n( "Invalid document. No mimetype specified." ) );
return 0;
} else if ( value == "application/x-vnd.kde.kplato.work" ) {
m_xmlLoader.setMimetype( value );
m_xmlLoader.setWorkVersion( plan.attribute( "version", "0.0.0" ) );
proj = new Project( m_config );
KPlatoXmlLoader loader( m_xmlLoader, proj );
ok = loader.loadWorkpackage( plan );
if ( ! ok ) {
setErrorMessage( loader.errorMessage() );
delete proj;
return 0;
}
package = loader.package();
package->timeTag = QDateTime::fromString( loader.timeTag(), Qt::ISODate );
} else if ( value != "application/x-vnd.kde.plan.work" ) {
debugPlan << "Unknown mime type " << value;
setErrorMessage( i18n( "Invalid document. Expected mimetype application/x-vnd.kde.plan.work, got %1", value ) );
return 0;
} else {
QString syntaxVersion = plan.attribute( "version", "0.0.0" );
m_xmlLoader.setWorkVersion( syntaxVersion );
if ( syntaxVersion > PLANWORK_FILE_SYNTAX_VERSION ) {
KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel(
0, i18n( "This document was created with a newer version of PlanWork (syntax version: %1)\n"
"Opening it in this version of PlanWork will lose some information.", syntaxVersion ),
i18n( "File-Format Mismatch" ), KGuiItem( i18n( "Continue" ) ) );
if ( ret == KMessageBox::Cancel ) {
setErrorMessage( "USER_CANCELED" );
return 0;
}
}
m_xmlLoader.setVersion( plan.attribute( "plan-version", PLAN_FILE_SYNTAX_VERSION ) );
m_xmlLoader.startLoad();
proj = new Project();
package = new Package();
package->project = proj;
KoXmlNode n = plan.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if ( e.tagName() == "project" ) {
m_xmlLoader.setProject( proj );
ok = proj->load( e, m_xmlLoader );
if ( ! ok ) {
m_xmlLoader.addMsg( XMLLoaderObject::Errors, "Loading of work package failed" );
//TODO add some ui here
}
} else if ( e.tagName() == "workpackage" ) {
package->timeTag = QDateTime::fromString( e.attribute( "time-tag" ), Qt::ISODate );
package->ownerId = e.attribute( "owner-id" );
package->ownerName = e.attribute( "owner" );
debugPlan<<"workpackage:"<timeTag<ownerId<ownerName;
KoXmlElement elem;
forEachElement( elem, e ) {
if ( elem.tagName() != "settings" ) {
continue;
}
package->settings.usedEffort = (bool)elem.attribute( "used-effort" ).toInt();
package->settings.progress = (bool)elem.attribute( "progress" ).toInt();
package->settings.documents = (bool)elem.attribute( "documents" ).toInt();
}
}
}
if ( proj->numChildren() > 0 ) {
package->task = static_cast( proj->childNode( 0 ) );
package->toTask = qobject_cast( m_project->findNode( package->task->id() ) );
WorkPackage &wp = package->task->workPackage();
if ( wp.ownerId().isEmpty() ) {
wp.setOwnerId( package->ownerId );
wp.setOwnerName( package->ownerName );
}
debugPlan<<"Task set:"<task->name();
}
m_xmlLoader.stopLoad();
}
if ( ok && proj->id() == project.id() && proj->childNode( 0 ) ) {
ok = project.nodeDict().contains( proj->childNode( 0 )->id() );
if ( ok && m_mergedPackages.contains( package->timeTag ) ) {
ok = false; // already merged
}
if ( ok && package->timeTag.isValid() && ! m_mergedPackages.contains( package->timeTag ) ) {
m_mergedPackages[ package->timeTag ] = proj; // register this for next time
}
if ( ok && ! package->timeTag.isValid() ) {
warnPlan<<"Work package is not time tagged:"<childNode( 0 )->name()<url;
ok = false;
}
}
if ( ! ok ) {
delete proj;
delete package;
return 0;
}
Q_ASSERT( package );
return package;
}
bool MainDocument::extractFiles( KoStore *store, Package *package )
{
if ( package->task == 0 ) {
errorPlan<<"No task!";
return false;
}
foreach ( Document *doc, package->task->documents().documents() ) {
if ( ! doc->isValid() || doc->type() != Document::Type_Product || doc->sendAs() != Document::SendAs_Copy ) {
continue;
}
if ( ! extractFile( store, package, doc ) ) {
return false;
}
}
return true;
}
bool MainDocument::extractFile( KoStore *store, Package *package, const Document *doc )
{
QTemporaryFile tmpfile;
if ( ! tmpfile.open() ) {
errorPlan<<"Failed to open temporary file";
return false;
}
if ( ! store->extractFile( doc->url().fileName(), tmpfile.fileName() ) ) {
errorPlan<<"Failed to extract file:"<url().fileName()<<"to:"<documents.insert( tmpfile.fileName(), doc->url() );
tmpfile.setAutoRemove( false );
debugPlan<<"extracted:"<url().fileName()<<"->"<numChildren() == 0 ) {
return;
}
m_checkingForWorkPackages = true;
if ( ! keep ) {
qDeleteAll( m_mergedPackages );
m_mergedPackages.clear();
}
QDir dir( m_config.retrieveUrl().path(), "*.planwork" );
m_infoList = dir.entryInfoList( QDir::Files | QDir::Readable, QDir::Time );
checkForWorkPackage();
return;
}
void MainDocument::checkForWorkPackage()
{
if ( ! m_infoList.isEmpty() ) {
loadWorkPackage( *m_project, QUrl::fromLocalFile( m_infoList.takeLast().absoluteFilePath() ) );
if ( ! m_infoList.isEmpty() ) {
QTimer::singleShot ( 0, this, SLOT(checkForWorkPackage()) );
return;
}
// all files read
// remove other projects
QMutableMapIterator it( m_workpackages );
while ( it.hasNext() ) {
it.next();
Package *package = it.value();
if ( package->project->id() != m_project->id() ) {
delete package->project;
delete package;
it.remove();
}
}
// Merge our workpackages
if ( ! m_workpackages.isEmpty() ) {
WorkPackageMergeDialog *dlg = new WorkPackageMergeDialog( i18n( "New work packages detected. Merge data with existing tasks?" ), m_workpackages );
connect(dlg, SIGNAL(finished(int)), SLOT(workPackageMergeDialogFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
}
}
void MainDocument::workPackageMergeDialogFinished( int result )
{
WorkPackageMergeDialog *dlg = qobject_cast( sender() );
if ( dlg == 0 ) {
return;
}
if ( result == KoDialog::Yes ) {
// merge the oldest first
foreach( int i, dlg->checkedList() ) {
mergeWorkPackage( m_workpackages.values().at( i ) );
}
// 'Yes' was hit so terminate all packages
foreach( const Package *p, m_workpackages.values() ) {
terminateWorkPackage( p );
}
}
qDeleteAll( m_workpackages );
m_workpackages.clear();
m_checkingForWorkPackages = false;
dlg->deleteLater();
}
void MainDocument::mergeWorkPackages()
{
foreach ( Package *package, m_workpackages ) {
mergeWorkPackage( package );
}
}
void MainDocument::terminateWorkPackage( const Package *package )
{
QFile file( package->url.path() );
if ( ! file.exists() ) {
return;
}
if ( KPlatoSettings::deleteFile() || KPlatoSettings::saveUrl().isEmpty() ) {
file.remove();
} else if ( KPlatoSettings::saveFile() && ! KPlatoSettings::saveUrl().isEmpty() ) {
QDir dir( KPlatoSettings::saveUrl().path() );
if ( ! dir.exists() ) {
if ( ! dir.mkpath( dir.path() ) ) {
//TODO message
debugPlan<<"Could not create directory:"<project);
if ( proj.id() == m_project->id() && proj.childNode( 0 ) ) {
const Task *from = package->task;
Task *to = package->toTask;
if ( to && from ) {
mergeWorkPackage( to, from, package );
}
}
}
void MainDocument::mergeWorkPackage( Task *to, const Task *from, const Package *package )
{
Resource *resource = m_project->findResource( package->ownerId );
if ( resource == 0 ) {
KMessageBox::error( 0, i18n( "The package owner '%1' is not a resource in this project. You must handle this manually.", package->ownerName ) );
return;
}
MacroCommand *cmd = new MacroCommand( kundo2_noi18n("Merge workpackage") );
Completion &org = to->completion();
const Completion &curr = from->completion();
if ( package->settings.progress ) {
if ( org.isStarted() != curr.isStarted() ) {
cmd->addCommand( new ModifyCompletionStartedCmd(org, curr.isStarted() ) );
}
if ( org.isFinished() != curr.isFinished() ) {
cmd->addCommand( new ModifyCompletionFinishedCmd( org, curr.isFinished() ) );
}
if ( org.startTime() != curr.startTime() ) {
cmd->addCommand( new ModifyCompletionStartTimeCmd( org, curr.startTime() ) );
}
if ( org.finishTime() != curr.finishTime() ) {
cmd->addCommand( new ModifyCompletionFinishTimeCmd( org, curr.finishTime() ) );
}
// TODO: review how/if to merge data from different resources
// remove entries
foreach ( const QDate &d, org.entries().keys() ) {
if ( ! curr.entries().contains( d ) ) {
debugPlan<<"remove entry "<addCommand( new RemoveCompletionEntryCmd( org, d ) );
}
}
// add new entries / modify existing
foreach ( const QDate &d, curr.entries().keys() ) {
if ( org.entries().contains( d ) && curr.entry( d ) == org.entry( d ) ) {
continue;
}
Completion::Entry *e = new Completion::Entry( *( curr.entry( d ) ) );
cmd->addCommand( new ModifyCompletionEntryCmd( org, d, e ) );
}
}
if ( package->settings.usedEffort ) {
Completion::UsedEffort *ue = new Completion::UsedEffort();
Completion::Entry prev;
Completion::EntryList::ConstIterator entriesIt = curr.entries().constBegin();
const Completion::EntryList::ConstIterator entriesEnd = curr.entries().constEnd();
for (; entriesIt != entriesEnd; ++entriesIt) {
const QDate &d = entriesIt.key();
const Completion::Entry &e = *entriesIt.value();
// set used effort from date entry and remove used effort from date entry
Completion::UsedEffort::ActualEffort effort( e.totalPerformed - prev.totalPerformed );
ue->setEffort( d, effort );
prev = e;
}
cmd->addCommand( new AddCompletionUsedEffortCmd( org, resource, ue ) );
}
bool docsaved = false;
if ( package->settings.documents ) {
//TODO: handle remote files
QMap::const_iterator it = package->documents.constBegin();
QMap::const_iterator end = package->documents.constEnd();
for ( ; it != end; ++it ) {
const QUrl src = QUrl::fromLocalFile(it.key());
KIO::CopyJob *job = KIO::move( src, it.value(), KIO::Overwrite );
if ( job->exec() ) {
docsaved = true;
//TODO: async
debugPlan<<"Moved file:"<isEmpty() ) {
KMessageBox::information( 0, i18n( "Nothing to save from this package" ) );
}
// add a copy to our tasks list of transmitted packages
WorkPackage *wp = new WorkPackage( from->workPackage() );
wp->setParentTask( to );
if ( ! wp->transmitionTime().isValid() ) {
wp->setTransmitionTime( package->timeTag );
}
wp->setTransmitionStatus( WorkPackage::TS_Receive );
cmd->addCommand( new WorkPackageAddCmd( m_project, to, wp ) );
addCommand( cmd );
}
void MainDocument::paintContent( QPainter &, const QRect &)
{
// Don't embed this app!!!
}
void MainDocument::slotViewDestroyed()
{
}
void MainDocument::setLoadingTemplate(bool loading)
{
m_loadingTemplate = loading;
}
void MainDocument::setLoadingSharedResourcesTemplate(bool loading)
{
m_loadingSharedResourcesTemplate = loading;
}
bool MainDocument::completeLoading( KoStore *store )
{
// If we get here the new project is loaded and set
if ( m_loadingTemplate ) {
//debugPlan<<"Loading template, generate unique ids";
m_project->generateUniqueIds();
m_project->setConstraintStartTime( QDateTime(QDate::currentDate(), QTime(0, 0, 0), Qt::LocalTime) );
m_project->setConstraintEndTime( m_project->constraintStartTime().addYears( 2 ) );
m_project->locale()->setCurrencyLocale(QLocale::AnyLanguage, QLocale::AnyCountry);
m_project->locale()->setCurrencySymbol(QString());
} else if ( isImporting() ) {
// NOTE: I don't think this is a good idea.
// Let the filter generate ids for non-plan files.
// If the user wants to create a new project from an old one,
// he should use Tools -> Insert Project File
//m_project->generateUniqueNodeIds();
}
if (m_loadingSharedResourcesTemplate && m_project->calendarCount() > 0) {
Calendar *c = m_project->calendarAt(0);
c->setTimeZone(QTimeZone::systemTimeZone());
}
if (m_project->useSharedResources() && !m_project->sharedResourcesFile().isEmpty()) {
QUrl url = QUrl::fromLocalFile(m_project->sharedResourcesFile());
if (url.isValid()) {
insertResourcesFile(url, m_project->sharedProjectsUrl());
}
}
if ( store == 0 ) {
// can happen if loading a template
debugPlan<<"No store";
return true; // continue anyway
}
delete m_context;
m_context = new Context();
KoXmlDocument doc;
if ( loadAndParse( store, "context.xml", doc ) ) {
store->close();
m_context->load( doc );
} else warnPlan<<"No context";
return true;
}
// TODO:
// Due to splitting of KoDocument into a document and a part,
// we simmulate the old behaviour by registering all views in the document.
// Find a better solution!
void MainDocument::registerView( View* view )
{
if ( view && ! m_views.contains( view ) ) {
m_views << QPointer( view );
}
}
bool MainDocument::completeSaving( KoStore *store )
{
foreach ( View *view, m_views ) {
if ( view ) {
if ( store->open( "context.xml" ) ) {
if ( m_context == 0 ) m_context = new Context();
QDomDocument doc = m_context->save( view );
KoStoreDevice dev( store );
QByteArray s = doc.toByteArray(); // this is already Utf8!
(void)dev.write( s.data(), s.size() );
(void)store->close();
m_viewlistModified = false;
emit viewlistModified( false );
}
break;
}
}
return true;
}
bool MainDocument::loadAndParse(KoStore *store, const QString &filename, KoXmlDocument &doc)
{
//debugPlan << "oldLoadAndParse: Trying to open " << filename;
if (!store->open(filename))
{
warnPlan << "Entry " << filename << " not found!";
// d->lastErrorMessage = i18n( "Could not find %1",filename );
return false;
}
// Error variables for QDomDocument::setContent
QString errorMsg;
int errorLine, errorColumn;
bool ok = doc.setContent( store->device(), &errorMsg, &errorLine, &errorColumn );
if ( !ok )
{
errorPlan << "Parsing error in " << filename << "! Aborting!" << endl
<< " In line: " << errorLine << ", column: " << errorColumn << endl
<< " Error message: " << errorMsg;
/* d->lastErrorMessage = i18n( "Parsing error in %1 at line %2, column %3\nError message: %4"
,filename ,errorLine, errorColumn ,
QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0,
QCoreApplication::UnicodeUTF8));*/
store->close();
return false;
}
debugPlan << "File " << filename << " loaded and parsed";
return true;
}
void MainDocument::insertFile( const QUrl &url, Node *parent, Node *after )
{
Part *part = new Part( this );
MainDocument *doc = new MainDocument( part );
part->setDocument( doc );
doc->disconnect(); // doc shall not handle feedback from openUrl()
doc->setAutoSave( 0 ); //disable
doc->m_insertFileInfo.url = url;
doc->m_insertFileInfo.parent = parent;
doc->m_insertFileInfo.after = after;
connect(doc, SIGNAL(completed()), SLOT(insertFileCompleted()));
connect(doc, SIGNAL(canceled(QString)), SLOT(insertFileCancelled(QString)));
doc->openUrl( url );
}
void MainDocument::insertFileCompleted()
{
debugPlan<( sender() );
if ( doc ) {
Project &p = doc->getProject();
insertProject( p, doc->m_insertFileInfo.parent, doc->m_insertFileInfo.after );
doc->documentPart()->deleteLater(); // also deletes document
} else {
KMessageBox::error( 0, i18n("Internal error, failed to insert file.") );
}
}
void MainDocument::insertResourcesFile(const QUrl &url, const QUrl &projects)
{
insertSharedProjects(projects); // prepare for insertion after shared resources
m_sharedProjectsFiles.removeAll(url); // resource file is not a project
Part *part = new Part( this );
MainDocument *doc = new MainDocument( part );
part->setDocument( doc );
doc->disconnect(); // doc shall not handle feedback from openUrl()
doc->setAutoSave( 0 ); //disable
connect(doc, SIGNAL(completed()), SLOT(insertResourcesFileCompleted()));
connect(doc, SIGNAL(canceled(QString)), SLOT(insertFileCancelled(QString)));
doc->openUrl( url );
}
void MainDocument::insertResourcesFileCompleted()
{
debugPlan<( sender() );
if (doc) {
Project &p = doc->getProject();
mergeResources(p);
m_project->setSharedResourcesLoaded(true);
doc->documentPart()->deleteLater(); // also deletes document
slotInsertSharedProject(); // insert shared bookings
} else {
KMessageBox::error( 0, i18n("Internal error, failed to insert file.") );
}
}
void MainDocument::insertFileCancelled( const QString &error )
{
debugPlan<( sender() );
if ( doc ) {
doc->documentPart()->deleteLater(); // also deletes document
}
}
void MainDocument::insertSharedProjects(const QUrl &url)
{
m_sharedProjectsFiles.clear();
QFileInfo fi(url.path());
if (!fi.exists()) {
- qInfo()<() << url;
debugPlan<<"Get all projects in file:"<resourceList()) {
r->clearExternalAppointments();
}
}
void MainDocument::slotInsertSharedProject()
{
debugPlan<setDocument( doc );
doc->disconnect(); // doc shall not handle feedback from openUrl()
doc->setAutoSave( 0 ); //disable
connect(doc, SIGNAL(completed()), SLOT(insertSharedProjectCompleted()));
connect(doc, SIGNAL(canceled(QString)), SLOT(insertSharedProjectCancelled(QString)));
doc->openUrl(m_sharedProjectsFiles.takeFirst());
}
void MainDocument::insertSharedProjectCompleted()
{
debugPlan<( sender() );
if (doc) {
Project &p = doc->getProject();
debugPlan<id()<<"Loaded project:"<id()) {
for (Resource *r : p.resourceList()) {
Resource *res = m_project->resource(r->id());
debugPlan<<"Resource:"<name()<<"->"<isShared()) {
for (const Appointment *a : r->appointments()) {
Appointment *app = new Appointment(*a);
app->setAuxcilliaryInfo(p.name());
res->addExternalAppointment(p.id(), app);
debugPlan<name()<<"added:"<auxcilliaryInfo()<documentPart()->deleteLater(); // also deletes document
emit insertSharedProject(); // do next file
} else {
KMessageBox::error( 0, i18n("Internal error, failed to insert file.") );
}
}
void MainDocument::insertSharedProjectCancelled( const QString &error )
{
debugPlan<( sender() );
if ( doc ) {
doc->documentPart()->deleteLater(); // also deletes document
}
}
bool MainDocument::insertProject( Project &project, Node *parent, Node *after )
{
debugPlan<<&project;
// make sure node ids in new project is unique also in old project
QList existingIds = m_project->nodeDict().keys();
foreach ( Node *n, project.allNodes() ) {
QString oldid = n->id();
n->setId( project.uniqueNodeId( existingIds ) );
project.removeId( oldid ); // remove old id
project.registerNodeId( n ); // register new id
}
MacroCommand *m = new InsertProjectCmd( project, parent==0?m_project:parent, after, kundo2_i18n( "Insert project" ) );
if ( m->isEmpty() ) {
delete m;
} else {
addCommand( m );
}
return true;
}
// check if calendar 'c' has children that will not be removed (normally 'Local' calendars)
bool canRemoveCalendar(const Calendar *c, const QList &lst)
{
for (Calendar *cc : c->calendars()) {
if (!lst.contains(cc)) {
return false;
}
if (!canRemoveCalendar(cc, lst)) {
return false;
}
}
return true;
}
// sort parent calendars before children
QList sortedRemoveCalendars(Project &shared, const QList &lst) {
QList result;
for (Calendar *c : lst) {
- if (!shared.calendar(c->id())) {
+ if (c->isShared() && !shared.calendar(c->id())) {
result << c;
}
result += sortedRemoveCalendars(shared, c->calendars());
}
return result;
}
bool MainDocument::mergeResources(Project &project)
{
debugPlan<<&project;
// Just in case, remove stuff not related to resources
for (Node *n : project.childNodeIterator()) {
delete n;
}
for (ScheduleManager *m : project.allScheduleManagers()) {
delete m;
}
// Mark all resources / groups as shared
for (ResourceGroup *g : project.resourceGroups()) {
g->setShared(true);
}
for (Resource *r : project.resourceList()) {
r->setShared(true);
}
// Mark all calendars shared
for (Calendar *c : project.allCalendars()) {
c->setShared(true);
}
// check if any shared stuff has been removed
QList removedGroups;
QList removedResources;
QList removedCalendars;
QStringList removed;
for (ResourceGroup *g : m_project->resourceGroups()) {
if (g->isShared() && !project.findResourceGroup(g->id())) {
removedGroups << g;
- removed << "Group: " + g->name();
+ removed << i18n("Group: %1", g->name());
}
}
for (Resource *r : m_project->resourceList()) {
if (r->isShared() && !project.findResource(r->id())) {
removedResources << r;
- removed << "Resource: " + r->name();
+ removed << i18n("Resource: %1", r->name());
}
}
removedCalendars = sortedRemoveCalendars(project, m_project->calendars());
for (Calendar *c : removedCalendars) {
- removed << "Calendar: " + c->name();
- }
- if (!removedCalendars.isEmpty() || !removedResources.isEmpty() || !removedGroups.isEmpty()) {
- // TODO show what has changed and give more detailed control
- if (QMessageBox::question(0, i18n("Shared resources removed"), i18n("Shall the removed shared resources be converted to local resources?")) == QMessageBox::Yes) {
+ removed << i18n("Calendar: %1", c->name());
+ }
+ if (!removed.isEmpty()) {
+ KMessageBox::ButtonCode result = KMessageBox::warningYesNoCancelList(
+ 0,
+ i18n("Shared resources has been removed from the shared resources file."
+ "\nSelect how they shall be treated in this project."),
+ removed,
+ xi18nc("@title:window", "Shared resources"),
+ KStandardGuiItem::remove(),
+ KGuiItem(i18n("Convert")),
+ KGuiItem(i18n("Keep"))
+ );
+ switch (result) {
+ case KMessageBox::Yes: // Remove
for (Resource *r : removedResources) {
- r->setShared(false);
+ RemoveResourceCmd cmd(r->parentGroup(), r);
+ cmd.redo();
}
for (ResourceGroup *g : removedGroups) {
if (g->resources().isEmpty()) {
+ RemoveResourceGroupCmd cmd(m_project, g);
+ cmd.redo();
+ } else {
+ // we may have put local resource(s) in this group
+ // so we need to keep it
g->setShared(false);
+ m_project->removeResourceGroupId(g->id());
+ g->setId(m_project->uniqueResourceGroupId());
+ m_project->insertResourceGroupId(g->id(), g);
}
}
+ for (Calendar *c : removedCalendars) {
+ CalendarRemoveCmd cmd(m_project, c);
+ cmd.redo();
+ }
+ break;
+ case KMessageBox::No: // Convert
+ for (Resource *r : removedResources) {
+ r->setShared(false);
+ m_project->removeResourceId(r->id());
+ r->setId(m_project->uniqueResourceId());
+ m_project->insertResourceId(r->id(), r);
+ }
+ for (ResourceGroup *g : removedGroups) {
+ g->setShared(false);
+ m_project->removeResourceGroupId(g->id());
+ g->setId(m_project->uniqueResourceGroupId());
+ m_project->insertResourceGroupId(g->id(), g);
+ }
for (Calendar *c : removedCalendars) {
c->setShared(false);
+ m_project->removeCalendarId(c->id());
+ c->setId(m_project->uniqueCalendarId());
+ m_project->insertCalendarId(c->id(), c);
}
+ break;
+ case KMessageBox::Cancel: // Keep
+ break;
+ default:
+ break;
}
}
// update values of already existsing objects
for (ResourceGroup *g : project.resourceGroups()) {
ResourceGroup *group = m_project->findResourceGroup(g->id());
if (group) {
group->setName(g->name());
group->setType(g->type());
}
}
for (Resource *r : project.resourceList()) {
Resource *resource = m_project->findResource(r->id());
if (resource) {
resource->setName(r->name());
resource->setInitials(r->initials());
resource->setEmail(r->email());
resource->setType(r->type());
resource->setAutoAllocate(r->autoAllocate());
resource->setAvailableFrom(r->availableFrom());
resource->setAvailableUntil(r->availableUntil());
resource->setUnits(r->units());
resource->setNormalRate(r->normalRate());
resource->setOvertimeRate(r->overtimeRate());
QString id = r->calendar(true) ? r->calendar(true)->id() : QString();
resource->setCalendar(m_project->findCalendar(id));
id = r->account() ? r->account()->name() : QString();
resource->setAccount(m_project->accounts().findAccount(id));
resource->setRequiredIds(r->requiredIds());
resource->setTeamMemberIds(r->teamMemberIds());
}
}
for (Calendar *c : project.allCalendars()) {
Calendar *calendar = m_project->findCalendar(c->id());
if (calendar) {
*calendar = *c;
}
}
// insert new objects
InsertProjectCmd cmd(project, m_project, 0);
cmd.execute();
return true;
}
void MainDocument::insertViewListItem( View */*view*/, const ViewListItem *item, const ViewListItem *parent, int index )
{
// FIXME callers should take care that they now get a signal even if originating from themselves
emit viewListItemAdded(item, parent, index);
setModified( true );
m_viewlistModified = true;
}
void MainDocument::removeViewListItem( View */*view*/, const ViewListItem *item )
{
// FIXME callers should take care that they now get a signal even if originating from themselves
emit viewListItemRemoved(item);
setModified( true );
m_viewlistModified = true;
}
void MainDocument::setModified( bool mod )
{
debugPlan<calendarCount() == 0)) {
// create a calendar
week = new Calendar(i18nc("Base calendar name", "Base"));
m_project->addCalendar(week);
CalendarDay vd(CalendarDay::NonWorking);
for (int i = Qt::Monday; i <= Qt::Sunday; ++i) {
if (m_config.isWorkingday(i)) {
CalendarDay wd(CalendarDay::Working);
TimeInterval ti(m_config.dayStartTime(i), m_config.dayLength(i));
wd.addInterval(ti);
week->setWeekday(i, wd);
} else {
week->setWeekday(i, vd);
}
}
}
}
#ifdef HAVE_KHOLIDAYS
if (KPlatoSettings::generateHolidays()) {
bool inweek = week != 0 && KPlatoSettings::generateHolidaysChoice() == KPlatoSettings::EnumGenerateHolidaysChoice::InWeekCalendar;
bool subcalendar = week != 0 && KPlatoSettings::generateHolidaysChoice() == KPlatoSettings::EnumGenerateHolidaysChoice::AsSubCalendar;
bool separate = week == 0 || KPlatoSettings::generateHolidaysChoice() == KPlatoSettings::EnumGenerateHolidaysChoice::AsSeparateCalendar;
Calendar *c = 0;
if (inweek) {
c = week;
qDebug()<addCalendar(c, week);
qDebug()<addCalendar(c);
qDebug()<setHolidayRegion(KPlatoSettings::region());
}
#endif
}
// creates a "new" project from current project (new ids etc)
void MainDocument::createNewProject()
{
setEmpty();
clearUndoHistory();
setModified( false );
resetURL();
KoDocumentInfo *info = documentInfo();
info->resetMetaData();
info->setProperty( "title", "" );
setTitleModified();
m_project->generateUniqueIds();
Duration dur = m_project->constraintEndTime() - m_project->constraintStartTime();
m_project->setConstraintStartTime( QDateTime(QDate::currentDate(), QTime(0, 0, 0), Qt::LocalTime) );
m_project->setConstraintEndTime( m_project->constraintStartTime() + dur );
while ( m_project->numScheduleManagers() > 0 ) {
foreach ( ScheduleManager *sm, m_project->allScheduleManagers() ) {
if ( sm->childCount() > 0 ) {
continue;
}
if ( sm->expected() ) {
sm->expected()->setDeleted( true );
sm->setExpected( 0 );
}
m_project->takeScheduleManager( sm );
delete sm;
}
}
foreach ( Schedule *s, m_project->schedules() ) {
m_project->takeSchedule( s );
delete s;
}
foreach ( Node *n, m_project->allNodes() ) {
foreach ( Schedule *s, n->schedules() ) {
n->takeSchedule( s );
delete s;
}
}
foreach ( Resource *r, m_project->resourceList() ) {
foreach ( Schedule *s, r->schedules().values() ) {
r->takeSchedule( s );
delete s;
}
}
}
} //KPlato namespace
diff --git a/plan/kptview.cpp b/plan/kptview.cpp
index 398dd117273..5dcda88215a 100644
--- a/plan/kptview.cpp
+++ b/plan/kptview.cpp
@@ -1,3224 +1,3224 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999, 2000 Torben Weis
Copyright (C) 2002 - 2011 Dag Andersen
Copyright (C) 2012 Dag Andersen
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kptview.h"
#include
#include
#include "KoDocumentInfo.h"
#include "KoMainWindow.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 "kptlocale.h"
#include "kptviewbase.h"
#include "kptaccountsview.h"
#include "kptaccountseditor.h"
#include "kptcalendareditor.h"
#include "kptfactory.h"
#include "kptmilestoneprogressdialog.h"
#include "kpttaskdescriptiondialog.h"
#include "kptnode.h"
#include "kptmaindocument.h"
#include "kptproject.h"
#include "kptmainprojectdialog.h"
#include "kpttask.h"
#include "kptsummarytaskdialog.h"
#include "kpttaskdialog.h"
#include "kpttaskprogressdialog.h"
#include "kptganttview.h"
#include "kpttaskeditor.h"
#include "kptdependencyeditor.h"
#include "kptperteditor.h"
#include "kptdatetime.h"
#include "kptcommand.h"
#include "kptrelation.h"
#include "kptrelationdialog.h"
#include "kptresourceappointmentsview.h"
#include "kptresourceeditor.h"
#include "kptscheduleeditor.h"
#include "kptresourcedialog.h"
#include "kptresource.h"
#include "kptstandardworktimedialog.h"
#include "kptwbsdefinitiondialog.h"
#include "kptresourceassignmentview.h"
#include "kpttaskstatusview.h"
#include "kptsplitterview.h"
#include "kptpertresult.h"
#include "ConfigProjectPanel.h"
#include "ConfigWorkVacationPanel.h"
#include "kpttaskdefaultpanel.h"
#include "kptworkpackageconfigpanel.h"
#include "kptcolorsconfigpanel.h"
#include "kptinsertfiledlg.h"
#include "kpthtmlview.h"
#include "about/aboutpage.h"
#include "kptlocaleconfigmoneydialog.h"
#include "kptflatproxymodel.h"
#include "kpttaskstatusmodel.h"
#include "reportsgenerator/ReportsGeneratorView.h"
#ifdef PLAN_USE_KREPORT
#include "reports/reportview.h"
#include "reports/reportdata.h"
#endif
#include "kptviewlistdialog.h"
#include "kptviewlistdocker.h"
#include "kptviewlist.h"
#include "kptschedulesdocker.h"
#include "kptpart.h"
#include "kptdebug.h"
#include "calligraplansettings.h"
#include "kptprintingcontrolprivate.h"
// #include "KPtViewAdaptor.h"
#include
namespace KPlato
{
//-------------------------------
ConfigDialog::ConfigDialog(QWidget *parent, const QString& name, KConfigSkeleton *config )
: KConfigDialog( parent, name, config ),
m_config( config )
{
KConfigDialogManager::changedMap()->insert("KRichTextWidget", SIGNAL(textChanged()) );
}
bool ConfigDialog::hasChanged()
{
QRegExp kcfg( "kcfg_*" );
foreach ( KRichTextWidget *w, findChildren( kcfg ) ) {
KConfigSkeletonItem *item = m_config->findItem( w->objectName().mid(5) );
if ( ! item->isEqual( w->toHtml() ) ) {
return true;
}
}
return false;
}
void ConfigDialog::updateSettings()
{
bool changed = false;
QRegExp kcfg( "kcfg_*" );
foreach ( KRichTextWidget *w, findChildren( kcfg ) ) {
KConfigSkeletonItem *item = m_config->findItem( w->objectName().mid(5) );
if ( ! item ) {
warnPlan << "The setting '" << w->objectName().mid(5) << "' has disappeared!";
continue;
}
if ( ! item->isEqual( QVariant( w->toHtml() ) ) ) {
item->setProperty( QVariant( w->toHtml() ) );
changed = true;
}
}
if ( changed ) {
m_config->save();
}
}
void ConfigDialog::updateWidgets()
{
QRegExp kcfg( "kcfg_*" );
foreach ( KRichTextWidget *w, findChildren( kcfg ) ) {
KConfigSkeletonItem *item = m_config->findItem( w->objectName().mid(5) );
if ( ! item ) {
warnPlan << "The setting '" << w->objectName().mid(5) << "' has disappeared!";
continue;
}
if ( ! item->isEqual( QVariant( w->toHtml() ) ) ) {
w->setHtml( item->property().toString() );
}
}
}
void ConfigDialog::updateWidgetsDefault()
{
bool usedefault = m_config->useDefaults( true );
updateWidgets();
m_config->useDefaults( usedefault );
}
bool ConfigDialog::isDefault()
{
bool bUseDefaults = m_config->useDefaults(true);
bool result = !hasChanged();
m_config->useDefaults(bUseDefaults);
return result;
}
//------------------------------------
View::View(KoPart *part, MainDocument *doc, QWidget *parent)
: KoView(part, doc, parent),
m_currentEstimateType( Estimate::Use_Expected ),
m_scheduleActionGroup( new QActionGroup( this ) ),
m_trigged( false ),
m_nextScheduleManager( 0 ),
m_readWrite( false ),
m_defaultView(1),
m_partpart (part)
{
//debugPlan;
doc->registerView( this );
setComponentName(Factory::global().componentName(), Factory::global().componentDisplayName());
if ( !doc->isReadWrite() )
setXMLFile( "calligraplan_readonly.rc" );
else
setXMLFile( "calligraplan.rc" );
// new ViewAdaptor( this );
m_sp = new QSplitter( this );
QVBoxLayout *layout = new QVBoxLayout( this );
layout->setMargin(0);
layout->addWidget( m_sp );
ViewListDocker *docker = 0;
if ( mainWindow() == 0 ) {
// Don't use docker if embedded
m_viewlist = new ViewListWidget(doc, m_sp);
m_viewlist->setProject( &( getProject() ) );
connect( m_viewlist, SIGNAL(selectionChanged(ScheduleManager*)), SLOT(slotSelectionChanged(ScheduleManager*)) );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), m_viewlist, SLOT(setSelectedSchedule(ScheduleManager*)) );
connect( m_viewlist, SIGNAL(updateViewInfo(ViewListItem*)), SLOT(slotUpdateViewInfo(ViewListItem*)) );
} else {
ViewListDockerFactory vl(this);
docker = static_cast(mainWindow()->createDockWidget(&vl));
if (docker->view() != this) {
docker->setView(this);
}
m_viewlist = docker->viewList();
#if 0 //SchedulesDocker
SchedulesDockerFactory sdf;
SchedulesDocker *sd = dynamic_cast( createDockWidget( &sdf ) );
Q_ASSERT( sd );
sd->setProject( &getProject() );
connect( sd, SIGNAL(selectionChanged(ScheduleManager*)), SLOT(slotSelectionChanged(ScheduleManager*)) );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), sd, SLOT(setSelectedSchedule(ScheduleManager*)) );
#endif
}
m_tab = new QStackedWidget( m_sp );
////////////////////////////////////////////////////////////////////////////////////////////////////
// Add sub views
createIntroductionView();
// The menu items
// ------ File
actionCreateTemplate = new QAction( i18n( "&Create Template From Document..." ), this );
actionCollection()->addAction("file_createtemplate", actionCreateTemplate );
connect( actionCreateTemplate, SIGNAL(triggered(bool)), SLOT(slotCreateTemplate()) );
actionCreateNewProject = new QAction( i18n( "&Create New Project..." ), this );
actionCollection()->addAction("file_createnewproject", actionCreateNewProject );
connect( actionCreateNewProject, SIGNAL(triggered(bool)), SLOT(slotCreateNewProject()) );
// ------ Edit
actionCut = actionCollection()->addAction(KStandardAction::Cut, "edit_cut", this, SLOT(slotEditCut()));
actionCopy = actionCollection()->addAction(KStandardAction::Copy, "edit_copy", this, SLOT(slotEditCopy()));
actionPaste = actionCollection()->addAction(KStandardAction::Paste, "edit_paste", this, SLOT(slotEditPaste()));
// ------ View
actionCollection()->addAction( KStandardAction::Redisplay, "view_refresh" , this, SLOT(slotRefreshView()) );
actionViewSelector = new KToggleAction(i18n("Show Selector"), this);
actionCollection()->addAction("view_show_selector", actionViewSelector );
connect( actionViewSelector, SIGNAL(triggered(bool)), SLOT(slotViewSelector(bool)) );
// ------ Insert
// ------ Project
actionEditMainProject = new QAction(koIcon("view-time-schedule-edit"), i18n("Edit Main Project..."), this);
actionCollection()->addAction("project_edit", actionEditMainProject );
connect( actionEditMainProject, SIGNAL(triggered(bool)), SLOT(slotProjectEdit()) );
actionEditStandardWorktime = new QAction(koIcon("configure"), i18n("Define Estimate Conversions..."), this);
actionCollection()->addAction("project_worktime", actionEditStandardWorktime );
connect( actionEditStandardWorktime, SIGNAL(triggered(bool)), SLOT(slotProjectWorktime()) );
// ------ Tools
actionDefineWBS = new QAction(koIcon("configure"), i18n("Define WBS Pattern..."), this);
actionCollection()->addAction("tools_define_wbs", actionDefineWBS );
connect( actionDefineWBS, SIGNAL(triggered(bool)), SLOT(slotDefineWBS()) );
actionInsertFile = new QAction(koIcon("document-import"), i18n("Insert Project File..."), this);
actionCollection()->addAction("insert_file", actionInsertFile );
connect( actionInsertFile, SIGNAL(triggered(bool)), SLOT(slotInsertFile()) );
// ------ Settings
actionConfigure = new QAction(koIcon("configure"), i18n("Configure Plan..."), this);
actionCollection()->addAction("configure", actionConfigure );
connect( actionConfigure, SIGNAL(triggered(bool)), SLOT(slotConfigure()) );
actionCurrencyConfig = new QAction(koIcon("configure"), i18n("Define Currency..."), this);
actionCollection()->addAction( "config_currency", actionCurrencyConfig );
connect( actionCurrencyConfig, SIGNAL(triggered(bool)), SLOT(slotCurrencyConfig()) );
#ifdef PLAN_USE_KREPORT
actionOpenReportFile = new QAction(koIcon("document-open"), i18n("Open Report Definition File..."), this);
actionCollection()->addAction( "reportdesigner_open_file", actionOpenReportFile );
connect( actionOpenReportFile, SIGNAL(triggered(bool)), SLOT(slotOpenReportFile()) );
#endif
// ------ Help
actionIntroduction = new QAction(koIcon("dialog-information"), i18n("Introduction to Plan"), this);
actionCollection()->addAction("plan_introduction", actionIntroduction );
connect( actionIntroduction, SIGNAL(triggered(bool)), SLOT(slotIntroduction()) );
// ------ Popup
actionOpenNode = new QAction(koIcon("document-edit"), i18n("Edit..."), this);
actionCollection()->addAction("node_properties", actionOpenNode );
connect( actionOpenNode, SIGNAL(triggered(bool)), SLOT(slotOpenNode()) );
actionTaskProgress = new QAction(koIcon("document-edit"), i18n("Progress..."), this);
actionCollection()->addAction("task_progress", actionTaskProgress );
connect( actionTaskProgress, SIGNAL(triggered(bool)), SLOT(slotTaskProgress()) );
actionDeleteTask = new QAction(koIcon("edit-delete"), i18n("Delete Task"), this);
actionCollection()->addAction("delete_task", actionDeleteTask );
connect( actionDeleteTask, SIGNAL(triggered(bool)), SLOT(slotDeleteTask()) );
actionTaskDescription = new QAction(koIcon("document-edit"), i18n("Description..."), this);
actionCollection()->addAction("task_description", actionTaskDescription );
connect( actionTaskDescription, SIGNAL(triggered(bool)), SLOT(slotTaskDescription()) );
actionIndentTask = new QAction(koIcon("format-indent-more"), i18n("Indent Task"), this);
actionCollection()->addAction("indent_task", actionIndentTask );
connect( actionIndentTask, SIGNAL(triggered(bool)), SLOT(slotIndentTask()) );
actionUnindentTask= new QAction(koIcon("format-indent-less"), i18n("Unindent Task"), this);
actionCollection()->addAction("unindent_task", actionUnindentTask );
connect( actionUnindentTask, SIGNAL(triggered(bool)), SLOT(slotUnindentTask()) );
actionMoveTaskUp = new QAction(koIcon("arrow-up"), i18n("Move Task Up"), this);
actionCollection()->addAction("move_task_up", actionMoveTaskUp );
connect( actionMoveTaskUp, SIGNAL(triggered(bool)), SLOT(slotMoveTaskUp()) );
actionMoveTaskDown = new QAction(koIcon("arrow-down"), i18n("Move Task Down"), this);
actionCollection()->addAction("move_task_down", actionMoveTaskDown );
connect( actionMoveTaskDown, SIGNAL(triggered(bool)), SLOT(slotMoveTaskDown()) );
actionEditResource = new QAction(koIcon("document-edit"), i18n("Edit Resource..."), this);
actionCollection()->addAction("edit_resource", actionEditResource );
connect( actionEditResource, SIGNAL(triggered(bool)), SLOT(slotEditResource()) );
actionEditRelation = new QAction(koIcon("document-edit"), i18n("Edit Dependency..."), this);
actionCollection()->addAction("edit_dependency", actionEditRelation );
connect( actionEditRelation, SIGNAL(triggered(bool)), SLOT(slotModifyRelation()) );
actionDeleteRelation = new QAction(koIcon("edit-delete"), i18n("Delete Dependency"), this);
actionCollection()->addAction("delete_dependency", actionDeleteRelation );
connect( actionDeleteRelation, SIGNAL(triggered(bool)), SLOT(slotDeleteRelation()) );
// Viewlist popup
connect( m_viewlist, SIGNAL(createView()), SLOT(slotCreateView()) );
m_estlabel = new QLabel( "", 0 );
if ( statusBar() ) {
addStatusBarItem( m_estlabel, 0, true );
}
connect( &getProject(), SIGNAL(scheduleChanged(MainSchedule*)), SLOT(slotScheduleChanged(MainSchedule*)) );
connect( &getProject(), SIGNAL(scheduleAdded(const MainSchedule*)), SLOT(slotScheduleAdded(const MainSchedule*)) );
connect( &getProject(), SIGNAL(scheduleRemoved(const MainSchedule*)), SLOT(slotScheduleRemoved(const MainSchedule*)) );
slotPlugScheduleActions();
connect( doc, SIGNAL(changed()), SLOT(slotUpdate()) );
connect( m_scheduleActionGroup, SIGNAL(triggered(QAction*)), SLOT(slotViewSchedule(QAction*)) );
connect( getPart(), SIGNAL(workPackageLoaded()), SLOT(slotWorkPackageLoaded()) );
// hide unused dockers
QTimer::singleShot( 0, this, SLOT(hideToolDocker()) );
// create views after dockers hidden, views take time for large projects
QTimer::singleShot( 100, this, SLOT(initiateViews()) );
const QList pluginFactories =
KoPluginLoader::instantiatePluginFactories(QStringLiteral("calligraplan/extensions"));
foreach (KPluginFactory* factory, pluginFactories) {
QObject *object = factory->create(this, QVariantList());
KXMLGUIClient *clientPlugin = dynamic_cast(object);
if (clientPlugin) {
insertChildClient(clientPlugin);
} else {
// not our/valid plugin, so delete the created object
object->deleteLater();
}
}
//debugPlan<<" end";
}
View::~View()
{
ViewBase *view = currentView();
if (view) {
// deactivate view to remove dockers etc
slotGuiActivated(view, false);
}
/* removeStatusBarItem( m_estlabel );
delete m_estlabel;*/
}
// hackish way to get rid of unused dockers, but as long as no official way exists...
void View::hideToolDocker()
{
if ( mainWindow() ) {
QStringList lst; lst << "KPlatoViewList" << "Scripting";
QStringList names;
foreach ( QDockWidget *w, mainWindow()->dockWidgets() ) {
if ( ! lst.contains( w->objectName() ) ) {
names << w->windowTitle();
w->setFeatures( QDockWidget::DockWidgetClosable );
w->hide();
}
}
foreach(const KActionCollection *c, KActionCollection::allCollections()) {
KActionMenu *a = qobject_cast(c->action("settings_dockers_menu"));
if ( a ) {
QList actions = a->menu()->actions();
foreach ( QAction *act, actions ) {
if ( names.contains( act->text() ) ) {
a->removeAction( act );
}
}
a->addSeparator();
break;
}
}
}
}
void View::initiateViews()
{
QApplication::setOverrideCursor( Qt::WaitCursor );
createViews();
connect( m_viewlist, SIGNAL(activated(ViewListItem*,ViewListItem*)), SLOT(slotViewActivated(ViewListItem*,ViewListItem*)) );
// after createViews() !!
connect( m_viewlist, SIGNAL(viewListItemRemoved(ViewListItem*)), SLOT(slotViewListItemRemoved(ViewListItem*)) );
// after createViews() !!
connect( m_viewlist, SIGNAL(viewListItemInserted(ViewListItem*,ViewListItem*,int)), SLOT(slotViewListItemInserted(ViewListItem*,ViewListItem*,int)) );
QDockWidget *docker = qobject_cast( m_viewlist->parent() );
if ( docker ) {
// after createViews() !!
connect( m_viewlist, SIGNAL(modified()), docker, SLOT(slotModified()));
connect( m_viewlist, SIGNAL(modified()), getPart(), SLOT(viewlistModified()));
connect(getPart(), SIGNAL(viewlistModified(bool)), docker, SLOT(updateWindowTitle(bool)));
}
connect( m_tab, SIGNAL(currentChanged(int)), this, SLOT(slotCurrentChanged(int)) );
slotSelectDefaultView();
loadContext();
QApplication::restoreOverrideCursor();
}
void View::slotCreateTemplate()
{
KoTemplateCreateDia::createTemplate(koDocument()->documentPart()->templatesResourcePath(), ".plant",
getPart(), this);
}
void View::slotCreateNewProject()
{
debugPlan;
if ( KMessageBox::Continue == KMessageBox::warningContinueCancel( this,
xi18nc( "@info",
"This action cannot be undone."
"Create a new Project from the current project "
"with new project- and task identities."
"Resource- and calendar identities are not changed."
"All scheduling information is removed."
"Do you want to continue?" ) ) )
{
getPart()->createNewProject();
slotOpenNode( &getProject() );
}
}
void View::createViews()
{
Context *ctx = getPart()->context();
if ( ctx && ctx->isLoaded() ) {
debugPlan<<"isLoaded";
KoXmlNode n = ctx->context().namedItem( "categories" );
if ( n.isNull() ) {
warnPlan<<"No categories";
} else {
n = n.firstChild();
for ( ; ! n.isNull(); n = n.nextSibling() ) {
if ( ! n.isElement() ) {
continue;
}
KoXmlElement e = n.toElement();
if (e.tagName() != "category") {
continue;
}
debugPlan<<"category: "<addCategory( ct, cn );
KoXmlNode n1 = e.firstChild();
for ( ; ! n1.isNull(); n1 = n1.nextSibling() ) {
if ( ! n1.isElement() ) {
continue;
}
KoXmlElement e1 = n1.toElement();
if (e1.tagName() != "view") {
continue;
}
ViewBase *v = 0;
QString type = e1.attribute( "viewtype" );
QString tag = e1.attribute( "tag" );
QString name = e1.attribute( "name" );
QString tip = e1.attribute( "tooltip" );
v = createView( cat, type, tag, name, tip );
//KoXmlNode settings = e1.namedItem( "settings " ); ????
KoXmlNode settings = e1.firstChild();
for ( ; ! settings.isNull(); settings = settings.nextSibling() ) {
if ( settings.nodeName() == "settings" ) {
break;
}
}
if ( v && settings.isElement() ) {
debugPlan<<" settings";
v->loadContext( settings.toElement() );
}
}
}
}
} else {
debugPlan<<"Default";
ViewListItem *cat;
QString ct = "Editors";
cat = m_viewlist->addCategory( ct, defaultCategoryInfo( ct ).name );
createCalendarEditor( cat, "CalendarEditor", QString(), TIP_USE_DEFAULT_TEXT );
createAccountsEditor( cat, "AccountsEditor", QString(), TIP_USE_DEFAULT_TEXT );
createResourceEditor( cat, "ResourceEditor", QString(), TIP_USE_DEFAULT_TEXT );
createTaskEditor( cat, "TaskEditor", QString(), TIP_USE_DEFAULT_TEXT );
createDependencyEditor( cat, "DependencyEditor", QString(), TIP_USE_DEFAULT_TEXT );
createPertEditor( cat, "PertEditor", QString(), TIP_USE_DEFAULT_TEXT );
createScheduleHandler( cat, "ScheduleHandlerView", QString(), TIP_USE_DEFAULT_TEXT );
ct = "Views";
cat = m_viewlist->addCategory( ct, defaultCategoryInfo( ct ).name );
createGanttView( cat, "GanttView", QString(), TIP_USE_DEFAULT_TEXT );
createMilestoneGanttView( cat, "MilestoneGanttView", QString(), TIP_USE_DEFAULT_TEXT );
createResourceAppointmentsView( cat, "ResourceAppointmentsView", QString(), TIP_USE_DEFAULT_TEXT );
createResourceAppointmentsGanttView( cat, "ResourceAppointmentsGanttView", QString(), TIP_USE_DEFAULT_TEXT );
createAccountsView( cat, "AccountsView", QString(), TIP_USE_DEFAULT_TEXT );
ct = "Execution";
cat = m_viewlist->addCategory( ct, defaultCategoryInfo( ct ).name );
createProjectStatusView( cat, "ProjectStatusView", QString(), TIP_USE_DEFAULT_TEXT );
createPerformanceStatusView( cat, "PerformanceStatusView", QString(), TIP_USE_DEFAULT_TEXT );
createTaskStatusView( cat, "TaskStatusView", QString(), TIP_USE_DEFAULT_TEXT );
createTaskView( cat, "TaskView", QString(), TIP_USE_DEFAULT_TEXT );
createTaskWorkPackageView( cat, "TaskWorkPackageView", QString(), TIP_USE_DEFAULT_TEXT );
ct = "Reports";
cat = m_viewlist->addCategory(ct, defaultCategoryInfo(ct).name);
createReportsGeneratorView(cat, "ReportsGeneratorView", i18n("Generate reports"), TIP_USE_DEFAULT_TEXT);
#ifdef PLAN_USE_KREPORT
// A little hack to get the user started...
ReportView *rv = qobject_cast( createReportView( cat, "ReportView", i18n( "Task Status Report" ), TIP_USE_DEFAULT_TEXT ) );
if ( rv ) {
QDomDocument doc;
doc.setContent( standardTaskStatusReport() );
rv->loadXML( doc );
}
#endif
}
}
ViewBase *View::createView( ViewListItem *cat, const QString &type, const QString &tag, const QString &name, const QString &tip, int index )
{
ViewBase *v = 0;
//NOTE: type is the same as classname (so if it is changed...)
if ( type == "CalendarEditor" ) {
v = createCalendarEditor( cat, tag, name, tip, index );
} else if ( type == "AccountsEditor" ) {
v = createAccountsEditor( cat, tag, name, tip, index );
} else if ( type == "ResourceEditor" ) {
v = createResourceEditor( cat, tag, name, tip, index );
} else if ( type == "TaskEditor" ) {
v = createTaskEditor( cat, tag, name, tip, index );
} else if ( type == "DependencyEditor" ) {
v = createDependencyEditor( cat, tag, name, tip, index );
} else if ( type == "PertEditor" ) {
v = createPertEditor( cat, tag, name, tip, index );
} else if ( type == "ScheduleEditor" ) {
v = createScheduleEditor( cat, tag, name, tip, index );
} else if ( type == "ScheduleHandlerView" ) {
v = createScheduleHandler( cat, tag, name, tip, index );
} else if ( type == "ProjectStatusView" ) {
v = createProjectStatusView( cat, tag, name, tip, index );
} else if ( type == "TaskStatusView" ) {
v = createTaskStatusView( cat, tag, name, tip, index );
} else if ( type == "TaskView" ) {
v = createTaskView( cat, tag, name, tip, index );
} else if ( type == "TaskWorkPackageView" ) {
v = createTaskWorkPackageView( cat, tag, name, tip, index );
} else if ( type == "GanttView" ) {
v = createGanttView( cat, tag, name, tip, index );
} else if ( type == "MilestoneGanttView" ) {
v = createMilestoneGanttView( cat, tag, name, tip, index );
} else if ( type == "ResourceAppointmentsView" ) {
v = createResourceAppointmentsView( cat, tag, name, tip, index );
} else if ( type == "ResourceAppointmentsGanttView" ) {
v = createResourceAppointmentsGanttView( cat, tag, name, tip, index );
} else if ( type == "AccountsView" ) {
v = createAccountsView( cat, tag, name, tip, index );
} else if ( type == "PerformanceStatusView" ) {
v = createPerformanceStatusView( cat, tag, name, tip, index );
} else if ( type == "ReportsGeneratorView" ) {
v = createReportsGeneratorView(cat, tag, name, tip, index);
} else if ( type == "ReportView" ) {
#ifdef PLAN_USE_KREPORT
v = createReportView( cat, tag, name, tip, index );
#endif
} else {
warnPlan<<"Unknown viewtype: "<type() == ViewListItem::ItemType_SubView ) {
itm->setViewInfo( defaultViewInfo( itm->viewType() ) );
} else if ( itm->type() == ViewListItem::ItemType_Category ) {
ViewInfo vi = defaultCategoryInfo( itm->tag() );
itm->setViewInfo( vi );
}
}
ViewInfo View::defaultViewInfo( const QString &type ) const
{
ViewInfo vi;
if ( type == "CalendarEditor" ) {
vi.name = i18n( "Work & Vacation" );
vi.tip = xi18nc( "@info:tooltip", "Edit working- and vacation days for resources" );
} else if ( type == "AccountsEditor" ) {
vi.name = i18n( "Cost Breakdown Structure" );
vi.tip = xi18nc( "@info:tooltip", "Edit cost breakdown structure." );
} else if ( type == "ResourceEditor" ) {
vi.name = i18n( "Resources" );
vi.tip = xi18nc( "@info:tooltip", "Edit resource breakdown structure" );
} else if ( type == "TaskEditor" ) {
vi.name = i18n( "Tasks" );
vi.tip = xi18nc( "@info:tooltip", "Edit work breakdown structure" );
} else if ( type == "DependencyEditor" ) {
vi.name = i18n( "Dependencies (Graphic)" );
vi.tip = xi18nc( "@info:tooltip", "Edit task dependencies" );
} else if ( type == "PertEditor" ) {
vi.name = i18n( "Dependencies (List)" );
vi.tip = xi18nc( "@info:tooltip", "Edit task dependencies" );
} else if ( type == "ScheduleEditor" ) {
// This view is not used stand-alone atm
vi.name = i18n( "Schedules" );
} else if ( type == "ScheduleHandlerView" ) {
vi.name = i18n( "Schedules" );
vi.tip = xi18nc( "@info:tooltip", "Calculate and analyze project schedules" );
} else if ( type == "ProjectStatusView" ) {
vi.name = i18n( "Project Performance Chart" );
vi.tip = xi18nc( "@info:tooltip", "View project status information" );
} else if ( type == "TaskStatusView" ) {
vi.name = i18n( "Task Status" );
vi.tip = xi18nc( "@info:tooltip", "View task progress information" );
} else if ( type == "TaskView" ) {
vi.name = i18n( "Task Execution" );
vi.tip = xi18nc( "@info:tooltip", "View task execution information" );
} else if ( type == "TaskWorkPackageView" ) {
vi.name = i18n( "Work Package View" );
vi.tip = xi18nc( "@info:tooltip", "View task work package information" );
} else if ( type == "GanttView" ) {
vi.name = i18n( "Gantt" );
vi.tip = xi18nc( "@info:tooltip", "View Gantt chart" );
} else if ( type == "MilestoneGanttView" ) {
vi.name = i18n( "Milestone Gantt" );
vi.tip = xi18nc( "@info:tooltip", "View milestone Gantt chart" );
} else if ( type == "ResourceAppointmentsView" ) {
vi.name = i18n( "Resource Assignments" );
vi.tip = xi18nc( "@info:tooltip", "View resource assignments in a table" );
} else if ( type == "ResourceAppointmentsGanttView" ) {
vi.name = i18n( "Resource Assignments (Gantt)" );
vi.tip = xi18nc( "@info:tooltip", "View resource assignments in Gantt chart" );
} else if ( type == "AccountsView" ) {
vi.name = i18n( "Cost Breakdown" );
vi.tip = xi18nc( "@info:tooltip", "View planned and actual cost" );
} else if ( type == "PerformanceStatusView" ) {
vi.name = i18n( "Tasks Performance Chart" );
vi.tip = xi18nc( "@info:tooltip", "View tasks performance status information" );
} else if ( type == "ReportsGeneratorView" ) {
vi.name = i18n( "Reports Generator" );
vi.tip = xi18nc( "@info:tooltip", "Generate reports" );
} else if ( type == "ReportView" ) {
vi.name = i18n( "Report" );
vi.tip = xi18nc( "@info:tooltip", "View report" );
} else {
warnPlan<<"Unknown viewtype: "<count()-1) : m_visitedViews.at(m_visitedViews.count() - 2);
debugPlan<<"Prev:"<setCurrentIndex(view);
return;
}
if ( url.url().startsWith( QLatin1String( "about:plan" ) ) ) {
getPart()->aboutPage().generatePage( v->htmlPart(), url );
return;
}
}
if ( url.scheme() == QLatin1String("help") ) {
KHelpClient::invokeHelp( "", url.fileName() );
return;
}
// try to open the url
debugPlan<htmlPart().setJScriptEnabled(false);
v->htmlPart().setJavaEnabled(false);
v->htmlPart().setMetaRefreshEnabled(false);
v->htmlPart().setPluginsEnabled(false);
slotOpenUrlRequest( v, QUrl( "about:plan/main" ) );
connect( v, SIGNAL(openUrlRequest(HtmlView*,QUrl)), SLOT(slotOpenUrlRequest(HtmlView*,QUrl)) );
m_tab->addWidget( v );
return v;
}
ViewBase *View::createResourceAppointmentsGanttView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
ResourceAppointmentsGanttView *v = new ResourceAppointmentsGanttView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ResourceAppointmentsGanttView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
connect( v, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), v, SLOT(setScheduleManager(ScheduleManager*)) );
connect( v, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
v->setProject( &( getProject() ) );
v->setScheduleManager( currentScheduleManager() );
v->updateReadWrite( m_readWrite );
return v;
}
ViewBase *View::createResourceAppointmentsView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
ResourceAppointmentsView *v = new ResourceAppointmentsView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ResourceAppointmentsView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
connect( v, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), v, SLOT(setScheduleManager(ScheduleManager*)) );
connect( v, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
v->setProject( &( getProject() ) );
v->setScheduleManager( currentScheduleManager() );
v->updateReadWrite( m_readWrite );
return v;
}
ViewBase *View::createResourceEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
ResourceEditor *resourceeditor = new ResourceEditor(getKoPart(), getPart(), m_tab );
m_tab->addWidget( resourceeditor );
resourceeditor->setProject( &(getProject()) );
ViewListItem *i = m_viewlist->addView( cat, tag, name, resourceeditor, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ResourceEditor" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
connect( resourceeditor, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( resourceeditor, SIGNAL(deleteObjectList(QObjectList)), SLOT(slotDeleteResourceObjects(QObjectList)) );
connect( resourceeditor, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
resourceeditor->updateReadWrite( m_readWrite );
return resourceeditor;
}
ViewBase *View::createTaskEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
TaskEditor *taskeditor = new TaskEditor(getKoPart(), getPart(), m_tab );
m_tab->addWidget( taskeditor );
m_defaultView = m_tab->count() - 1;
ViewListItem *i = m_viewlist->addView( cat, tag, name, taskeditor, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "TaskEditor" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
taskeditor->setProject( &(getProject()) );
taskeditor->setScheduleManager( currentScheduleManager() );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), taskeditor, SLOT(setScheduleManager(ScheduleManager*)) );
connect( taskeditor, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( taskeditor, SIGNAL(addTask()), SLOT(slotAddTask()) );
connect( taskeditor, SIGNAL(addMilestone()), SLOT(slotAddMilestone()) );
connect( taskeditor, SIGNAL(addSubtask()), SLOT(slotAddSubTask()) );
connect( taskeditor, SIGNAL(addSubMilestone()), SLOT(slotAddSubMilestone()) );
connect( taskeditor, SIGNAL(deleteTaskList(QList)), SLOT(slotDeleteTask(QList)) );
connect( taskeditor, SIGNAL(moveTaskUp()), SLOT(slotMoveTaskUp()) );
connect( taskeditor, SIGNAL(moveTaskDown()), SLOT(slotMoveTaskDown()) );
connect( taskeditor, SIGNAL(indentTask()), SLOT(slotIndentTask()) );
connect( taskeditor, SIGNAL(unindentTask()), SLOT(slotUnindentTask()) );
connect(taskeditor, SIGNAL(saveTaskModule(QUrl,Project*)), SLOT(saveTaskModule(QUrl,Project*)));
connect(taskeditor, SIGNAL(removeTaskModule(QUrl)), SLOT(removeTaskModule(QUrl)));
connect( taskeditor, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
taskeditor->updateReadWrite( m_readWrite );
// last:
QStringList modules = KoResourcePaths::findAllResources( "calligraplan_taskmodules", "*.plan", KoResourcePaths::NoDuplicates|KoResourcePaths::Recursive );
debugPlan<setTaskModules( modules );
return taskeditor;
}
ViewBase *View::createAccountsEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
AccountsEditor *ae = new AccountsEditor(getKoPart(), getPart(), m_tab );
m_tab->addWidget( ae );
ViewListItem *i = m_viewlist->addView( cat, tag, name, ae, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "AccountsEditor" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
ae->draw( getProject() );
connect( ae, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
ae->updateReadWrite( m_readWrite );
return ae;
}
ViewBase *View::createCalendarEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
CalendarEditor *calendareditor = new CalendarEditor(getKoPart(), getPart(), m_tab );
m_tab->addWidget( calendareditor );
ViewListItem *i = m_viewlist->addView( cat, tag, name, calendareditor, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "CalendarEditor" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
calendareditor->draw( getProject() );
connect( calendareditor, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( calendareditor, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
calendareditor->updateReadWrite( m_readWrite );
return calendareditor;
}
ViewBase *View::createScheduleHandler( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
ScheduleHandlerView *handler = new ScheduleHandlerView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( handler );
ViewListItem *i = m_viewlist->addView( cat, tag, name, handler, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ScheduleHandlerView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
connect( handler->scheduleEditor(), SIGNAL(addScheduleManager(Project*)), SLOT(slotAddScheduleManager(Project*)) );
connect( handler->scheduleEditor(), SIGNAL(deleteScheduleManager(Project*,ScheduleManager*)), SLOT(slotDeleteScheduleManager(Project*,ScheduleManager*)) );
connect( handler->scheduleEditor(), SIGNAL(moveScheduleManager(ScheduleManager*,ScheduleManager*,int)), SLOT(slotMoveScheduleManager(ScheduleManager*,ScheduleManager*,int)));
connect( handler->scheduleEditor(), SIGNAL(calculateSchedule(Project*,ScheduleManager*)), SLOT(slotCalculateSchedule(Project*,ScheduleManager*)) );
connect( handler->scheduleEditor(), SIGNAL(baselineSchedule(Project*,ScheduleManager*)), SLOT(slotBaselineSchedule(Project*,ScheduleManager*)) );
connect( handler, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), handler, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)) );
connect( handler, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
connect(handler, SIGNAL(editNode(Node*)), this, SLOT(slotOpenNode(Node*)));
connect(handler, SIGNAL(editResource(Resource*)), this, SLOT(slotEditResource(Resource*)));
handler->draw( getProject() );
handler->updateReadWrite( m_readWrite );
return handler;
}
ScheduleEditor *View::createScheduleEditor( QWidget *parent )
{
ScheduleEditor *scheduleeditor = new ScheduleEditor(getKoPart(), getPart(), parent );
connect( scheduleeditor, SIGNAL(addScheduleManager(Project*)), SLOT(slotAddScheduleManager(Project*)) );
connect( scheduleeditor, SIGNAL(deleteScheduleManager(Project*,ScheduleManager*)), SLOT(slotDeleteScheduleManager(Project*,ScheduleManager*)) );
connect( scheduleeditor, SIGNAL(calculateSchedule(Project*,ScheduleManager*)), SLOT(slotCalculateSchedule(Project*,ScheduleManager*)) );
connect( scheduleeditor, SIGNAL(baselineSchedule(Project*,ScheduleManager*)), SLOT(slotBaselineSchedule(Project*,ScheduleManager*)) );
scheduleeditor->updateReadWrite( m_readWrite );
return scheduleeditor;
}
ViewBase *View::createScheduleEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
ScheduleEditor *scheduleeditor = new ScheduleEditor(getKoPart(), getPart(), m_tab );
m_tab->addWidget( scheduleeditor );
ViewListItem *i = m_viewlist->addView( cat, tag, name, scheduleeditor, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ScheduleEditor" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
scheduleeditor->setProject( &( getProject() ) );
connect( scheduleeditor, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( scheduleeditor, SIGNAL(addScheduleManager(Project*)), SLOT(slotAddScheduleManager(Project*)) );
connect( scheduleeditor, SIGNAL(deleteScheduleManager(Project*,ScheduleManager*)), SLOT(slotDeleteScheduleManager(Project*,ScheduleManager*)) );
connect( scheduleeditor, SIGNAL(calculateSchedule(Project*,ScheduleManager*)), SLOT(slotCalculateSchedule(Project*,ScheduleManager*)) );
connect( scheduleeditor, SIGNAL(baselineSchedule(Project*,ScheduleManager*)), SLOT(slotBaselineSchedule(Project*,ScheduleManager*)) );
scheduleeditor->updateReadWrite( m_readWrite );
return scheduleeditor;
}
ViewBase *View::createDependencyEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
DependencyEditor *editor = new DependencyEditor(getKoPart(), getPart(), m_tab );
m_tab->addWidget( editor );
ViewListItem *i = m_viewlist->addView( cat, tag, name, editor, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "DependencyEditor" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
editor->draw( getProject() );
connect( editor, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( editor, SIGNAL(addRelation(Node*,Node*,int)), SLOT(slotAddRelation(Node*,Node*,int)) );
connect( editor, SIGNAL(modifyRelation(Relation*,int)), SLOT(slotModifyRelation(Relation*,int)) );
connect( editor, SIGNAL(modifyRelation(Relation*)), SLOT(slotModifyRelation(Relation*)) );
connect( editor, SIGNAL(editNode(Node*)), SLOT(slotOpenNode(Node*)) );
connect( editor, SIGNAL(addTask()), SLOT(slotAddTask()) );
connect( editor, SIGNAL(addMilestone()), SLOT(slotAddMilestone()) );
connect( editor, SIGNAL(addSubMilestone()), SLOT(slotAddSubMilestone()) );
connect( editor, SIGNAL(addSubtask()), SLOT(slotAddSubTask()) );
connect( editor, SIGNAL(deleteTaskList(QList)), SLOT(slotDeleteTask(QList)) );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), editor, SLOT(setScheduleManager(ScheduleManager*)) );
connect( editor, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
editor->updateReadWrite( m_readWrite );
editor->setScheduleManager( currentScheduleManager() );
return editor;
}
ViewBase *View::createPertEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
PertEditor *perteditor = new PertEditor(getKoPart(), getPart(), m_tab );
m_tab->addWidget( perteditor );
ViewListItem *i = m_viewlist->addView( cat, tag, name, perteditor, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "PertEditor" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
perteditor->draw( getProject() );
connect( perteditor, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
m_updatePertEditor = true;
perteditor->updateReadWrite( m_readWrite );
return perteditor;
}
ViewBase *View::createProjectStatusView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
ProjectStatusView *v = new ProjectStatusView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ProjectStatusView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
connect( v, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), v, SLOT(setScheduleManager(ScheduleManager*)) );
v->updateReadWrite( m_readWrite );
v->setProject( &getProject() );
v->setScheduleManager( currentScheduleManager() );
return v;
}
ViewBase *View::createPerformanceStatusView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
PerformanceStatusView *v = new PerformanceStatusView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "PerformanceStatusView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
connect( v, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), v, SLOT(setScheduleManager(ScheduleManager*)) );
connect( v, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
v->updateReadWrite( m_readWrite );
v->setProject( &getProject() );
v->setScheduleManager( currentScheduleManager() );
return v;
}
ViewBase *View::createTaskStatusView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
TaskStatusView *taskstatusview = new TaskStatusView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( taskstatusview );
ViewListItem *i = m_viewlist->addView( cat, tag, name, taskstatusview, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "TaskStatusView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
connect( taskstatusview, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), taskstatusview, SLOT(setScheduleManager(ScheduleManager*)) );
connect( taskstatusview, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
taskstatusview->updateReadWrite( m_readWrite );
taskstatusview->draw( getProject() );
taskstatusview->setScheduleManager( currentScheduleManager() );
return taskstatusview;
}
ViewBase *View::createTaskView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
TaskView *v = new TaskView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "TaskView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
v->draw( getProject() );
v->setScheduleManager( currentScheduleManager() );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), v, SLOT(setScheduleManager(ScheduleManager*)) );
connect( v, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( v, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
v->updateReadWrite( m_readWrite );
return v;
}
ViewBase *View::createTaskWorkPackageView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
TaskWorkPackageView *v = new TaskWorkPackageView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "TaskWorkPackageView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
v->setProject( &getProject() );
v->setScheduleManager( currentScheduleManager() );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), v, SLOT(setScheduleManager(ScheduleManager*)) );
connect( v, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( v, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
connect( v, SIGNAL(mailWorkpackage(Node*,Resource*)), SLOT(slotMailWorkpackage(Node*,Resource*)) );
connect( v, SIGNAL(mailWorkpackages(QList,Resource*)), SLOT(slotMailWorkpackages(QList,Resource*)) );
connect(v, SIGNAL(checkForWorkPackages()), getPart(), SLOT(checkForWorkPackages()));
v->updateReadWrite( m_readWrite );
return v;
}
ViewBase *View::createGanttView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
GanttView *ganttview = new GanttView(getKoPart(), getPart(), m_tab, koDocument()->isReadWrite() );
m_tab->addWidget( ganttview );
ViewListItem *i = m_viewlist->addView( cat, tag, name, ganttview, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "GanttView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
ganttview->setProject( &( getProject() ) );
ganttview->setScheduleManager( currentScheduleManager() );
connect( ganttview, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
/* TODO: Review these
connect( ganttview, SIGNAL(addRelation(Node*,Node*,int)), SLOT(slotAddRelation(Node*,Node*,int)) );
connect( ganttview, SIGNAL(modifyRelation(Relation*,int)), SLOT(slotModifyRelation(Relation*,int)) );
connect( ganttview, SIGNAL(modifyRelation(Relation*)), SLOT(slotModifyRelation(Relation*)) );
connect( ganttview, SIGNAL(itemDoubleClicked()), SLOT(slotOpenNode()) );
connect( ganttview, SIGNAL(itemRenamed(Node*,QString)), this, SLOT(slotRenameNode(Node*,QString)) );*/
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), ganttview, SLOT(setScheduleManager(ScheduleManager*)) );
connect( ganttview, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
ganttview->updateReadWrite( m_readWrite );
return ganttview;
}
ViewBase *View::createMilestoneGanttView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
MilestoneGanttView *ganttview = new MilestoneGanttView(getKoPart(), getPart(), m_tab, koDocument()->isReadWrite() );
m_tab->addWidget( ganttview );
ViewListItem *i = m_viewlist->addView( cat, tag, name, ganttview, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "MilestoneGanttView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
ganttview->setProject( &( getProject() ) );
ganttview->setScheduleManager( currentScheduleManager() );
connect( ganttview, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), ganttview, SLOT(setScheduleManager(ScheduleManager*)) );
connect( ganttview, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
ganttview->updateReadWrite( m_readWrite );
return ganttview;
}
ViewBase *View::createAccountsView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
AccountsView *accountsview = new AccountsView(getKoPart(), &getProject(), getPart(), m_tab );
m_tab->addWidget( accountsview );
ViewListItem *i = m_viewlist->addView( cat, tag, name, accountsview, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "AccountsView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
accountsview->setScheduleManager( currentScheduleManager() );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), accountsview, SLOT(setScheduleManager(ScheduleManager*)) );
connect( accountsview, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
accountsview->updateReadWrite( m_readWrite );
return accountsview;
}
ViewBase *View::createResourceAssignmentView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
ResourceAssignmentView *resourceAssignmentView = new ResourceAssignmentView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( resourceAssignmentView );
m_updateResourceAssignmentView = true;
ViewListItem *i = m_viewlist->addView( cat, tag, name, resourceAssignmentView, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ResourceAssignmentView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
resourceAssignmentView->draw( getProject() );
connect( resourceAssignmentView, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( resourceAssignmentView, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
resourceAssignmentView->updateReadWrite( m_readWrite );
return resourceAssignmentView;
}
ViewBase *View::createReportsGeneratorView(ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index)
{
ReportsGeneratorView *v = new ReportsGeneratorView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView(cat, tag, name, v, getPart(), "", index);
ViewInfo vi = defaultViewInfo( "ReportsGeneratorView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
v->setProject( &getProject() );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), v, SLOT(setScheduleManager(ScheduleManager*)) );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), v, SLOT(slotRefreshView()));
v->setScheduleManager( currentScheduleManager() );
connect( v, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
connect( v, SIGNAL(requestPopupMenu(QString,QPoint)), this, SLOT(slotPopupMenu(QString,QPoint)) );
v->updateReadWrite( m_readWrite );
return v;
}
ViewBase *View::createReportView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index )
{
#ifdef PLAN_USE_KREPORT
ReportView *v = new ReportView(getKoPart(), getPart(), m_tab );
m_tab->addWidget( v );
ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index );
ViewInfo vi = defaultViewInfo( "ReportView" );
if ( name.isEmpty() ) {
i->setText( 0, vi.name );
}
if ( tip == TIP_USE_DEFAULT_TEXT ) {
i->setToolTip( 0, vi.tip );
} else {
i->setToolTip( 0, tip );
}
v->setProject( &getProject() );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), v, SLOT(setScheduleManager(ScheduleManager*)) );
connect( this, SIGNAL(currentScheduleManagerChanged(ScheduleManager*)), v, SLOT(slotRefreshView()));
v->setScheduleManager( currentScheduleManager() );
connect( v, SIGNAL(guiActivated(ViewBase*,bool)), SLOT(slotGuiActivated(ViewBase*,bool)) );
v->updateReadWrite( m_readWrite );
return v;
#else
return 0;
#endif
}
Project& View::getProject() const
{
return getPart() ->getProject();
}
KoPrintJob * View::createPrintJob()
{
KoView *v = qobject_cast( canvas() );
if ( v == 0 ) {
return 0;
}
return v->createPrintJob();
}
ViewBase *View::currentView() const
{
return qobject_cast( m_tab->currentWidget() );
}
void View::slotEditCut()
{
ViewBase *v = currentView();
if ( v ) {
v->slotEditCut();
}
}
void View::slotEditCopy()
{
ViewBase *v = currentView();
if ( v ) {
v->slotEditCopy();
}
}
void View::slotEditPaste()
{
ViewBase *v = currentView();
if ( v ) {
v->slotEditPaste();
}
}
void View::slotRefreshView()
{
ViewBase *v = currentView();
if ( v ) {
debugPlan<slotRefreshView();
}
}
void View::slotViewSelector( bool show )
{
//debugPlan;
m_viewlist->setVisible( show );
}
void View::slotInsertResourcesFile(const QString &file, const QUrl &projects)
{
getPart()->insertResourcesFile(QUrl(file), projects);
}
void View::slotInsertFile()
{
InsertFileDialog *dlg = new InsertFileDialog( getProject(), currentTask(), this );
connect(dlg, SIGNAL(finished(int)), SLOT(slotInsertFileFinished(int)));
dlg->show();
dlg->raise();
dlg->activateWindow();
}
void View::slotInsertFileFinished( int result )
{
InsertFileDialog *dlg = qobject_cast( sender() );
if ( dlg == 0 ) {
return;
}
if ( result == QDialog::Accepted ) {
getPart()->insertFile( dlg->url(), dlg->parentNode(), dlg->afterNode() );
}
dlg->deleteLater();
}
void View::slotProjectEdit()
{
slotOpenNode( &getProject() );
}
void View::slotProjectWorktime()
{
StandardWorktimeDialog *dia = new StandardWorktimeDialog( getProject(), this );
connect(dia, SIGNAL(finished(int)), this, SLOT(slotProjectWorktimeFinished(int)));
dia->show();
dia->raise();
dia->activateWindow();
}
void View::slotProjectWorktimeFinished( int result )
{
StandardWorktimeDialog *dia = qobject_cast( sender() );
if ( dia == 0 ) {
return;
}
if ( result == QDialog::Accepted) {
KUndo2Command * cmd = dia->buildCommand();
if ( cmd ) {
//debugPlan<<"Modifying calendar(s)";
getPart() ->addCommand( cmd ); //also executes
}
}
dia->deleteLater();
}
void View::slotSelectionChanged( ScheduleManager *sm ) {
debugPlan<expected() );
if ( idx < 0 ) {
debugPlan<expected();
return;
}
QAction *a = m_scheduleActions.keys().at( idx );
Q_ASSERT( a );
a->setChecked( true ); // this doesn't trigger QActionGroup
slotViewSchedule( a );
}
QList View::sortedActionList()
{
QMap lst;
foreach ( QAction *a, m_scheduleActions.keys() ) {
lst.insert( a->objectName(), a );
}
return lst.values();
}
void View::slotScheduleRemoved( const MainSchedule *sch )
{
debugPlan<name();
QAction *a = 0;
QAction *checked = m_scheduleActionGroup->checkedAction();
QMapIterator i( m_scheduleActions );
while (i.hasNext()) {
i.next();
if ( i.value() == sch ) {
a = i.key();
break;
}
}
if ( a ) {
unplugActionList( "view_schedule_list" );
delete a;
plugActionList( "view_schedule_list", sortedActionList() );
if ( checked && checked != a ) {
checked->setChecked( true );
} else if ( ! m_scheduleActions.isEmpty() ) {
m_scheduleActions.keys().first()->setChecked( true );
}
}
slotViewSchedule( m_scheduleActionGroup->checkedAction() );
}
void View::slotScheduleAdded( const MainSchedule *sch )
{
if ( sch->type() != Schedule::Expected ) {
return; // Only view expected
}
MainSchedule *s = const_cast( sch );
// debugPlan<name()<<" deleted="<isDeleted()<<"scheduled="<isScheduled();
QAction *checked = m_scheduleActionGroup->checkedAction();
if ( ! sch->isDeleted() && sch->isScheduled() ) {
unplugActionList( "view_schedule_list" );
QAction *act = addScheduleAction( s );
plugActionList( "view_schedule_list", sortedActionList() );
if ( checked ) {
checked->setChecked( true );
} else if ( act ) {
act->setChecked( true );
} else if ( ! m_scheduleActions.isEmpty() ) {
m_scheduleActions.keys().first()->setChecked( true );
}
}
slotViewSchedule( m_scheduleActionGroup->checkedAction() );
}
void View::slotScheduleChanged( MainSchedule *sch )
{
// debugPlan<name()<<" deleted="<