1 /*
2    Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
3    Copyright (c) 2011 Martin Koller <kollix@aon.at>
4    All rights reserved.
5 
6    Redistribution and use in source and binary forms, with or without
7    modification, are permitted provided that the following conditions
8    are met:
9 
10    1. Redistributions of source code must retain the above copyright
11       notice, this list of conditions and the following disclaimer.
12    2. Redistributions in binary form must reproduce the above copyright
13       notice, this list of conditions and the following disclaimer in the
14       documentation and/or other materials provided with the distribution.
15 
16    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 
28 
29 #include "kpMainWindow.h"
30 #include "kpMainWindowPrivate.h"
31 
32 #include <QApplication>
33 #include <QClipboard>
34 #include <QFontMetrics>
35 #include <QImage>
36 #include <QList>
37 #include <QMenu>
38 #include <QDesktopWidget>
39 #include <QScrollBar>
40 
41 #include "kpLogCategories.h"
42 #include <KMessageBox>
43 #include <KStandardAction>
44 #include <KActionCollection>
45 #include <KXMLGUIFactory>
46 #include <KLocalizedString>
47 
48 #include "layers/selections/image/kpAbstractImageSelection.h"
49 #include "widgets/toolbars/kpColorToolBar.h"
50 #include "commands/kpCommandHistory.h"
51 #include "document/kpDocument.h"
52 #include "imagelib/kpDocumentMetaInfo.h"
53 #include "document/kpDocumentSaveOptions.h"
54 #include "layers/selections/image/kpImageSelectionTransparency.h"
55 #include "commands/kpMacroCommand.h"
56 #include "pixmapfx/kpPixmapFX.h"
57 #include "layers/selections/image/kpRectangularImageSelection.h"
58 #include "layers/selections/kpSelectionDrag.h"
59 #include "generic/kpSetOverrideCursorSaver.h"
60 #include "layers/selections/text/kpTextSelection.h"
61 #include "tools/kpTool.h"
62 #include "commands/tools/selection/text/kpToolTextGiveContentCommand.h"
63 #include "commands/tools/selection/kpToolSelectionCreateCommand.h"
64 #include "commands/tools/selection/kpToolSelectionDestroyCommand.h"
65 #include "commands/tools/selection/text/kpToolTextEnterCommand.h"
66 #include "commands/tools/selection/text/kpToolTextInsertCommand.h"
67 #include "imagelib/transforms/kpTransformCrop.h"
68 #include "commands/imagelib/transforms/kpTransformResizeScaleCommand.h"
69 #include "views/manager/kpViewManager.h"
70 #include "kpViewScrollableContainer.h"
71 #include "views/kpZoomedView.h"
72 
73 //---------------------------------------------------------------------
74 
75 // private
setupEditMenuActions()76 void kpMainWindow::setupEditMenuActions ()
77 {
78     KActionCollection *ac = actionCollection ();
79 
80 
81     // Undo/Redo
82     // CONFIG: Need GUI for config history size.
83     d->commandHistory = new kpCommandHistory (true/*read config*/, this);
84 
85     if (d->configFirstTime)
86     {
87         // (so that cfg-file-editing user can modify in the meantime)
88         d->commandHistory->writeConfig ();
89     }
90 
91 
92     d->actionCut = KStandardAction::cut (this, SLOT (slotCut()), ac);
93     d->actionCopy = KStandardAction::copy (this, SLOT (slotCopy()), ac);
94     d->actionPaste = KStandardAction::paste (this, SLOT (slotPaste()), ac);
95     d->actionPasteInNewWindow = ac->addAction (QStringLiteral("edit_paste_in_new_window"));
96     d->actionPasteInNewWindow->setText (i18n ("Paste in &New Window"));
97     connect (d->actionPasteInNewWindow, &QAction::triggered,
98              this, &kpMainWindow::slotPasteInNewWindow);
99     ac->setDefaultShortcut (d->actionPasteInNewWindow, Qt::CTRL | Qt::SHIFT | Qt::Key_V);
100 
101     //d->actionDelete = KStandardAction::clear (this, SLOT (slotDelete()), ac);
102     d->actionDelete = ac->addAction (QStringLiteral("edit_clear"));
103     d->actionDelete->setText (i18n ("&Delete Selection"));
104     connect (d->actionDelete, &QAction::triggered, this, &kpMainWindow::slotDelete);
105 
106     d->actionSelectAll = KStandardAction::selectAll (this, SLOT (slotSelectAll()), ac);
107     d->actionDeselect = KStandardAction::deselect (this, SLOT (slotDeselect()), ac);
108 
109 
110     d->actionCopyToFile = ac->addAction (QStringLiteral("edit_copy_to_file"));
111     d->actionCopyToFile->setText (i18n ("C&opy to File..."));
112     connect (d->actionCopyToFile, &QAction::triggered, this, &kpMainWindow::slotCopyToFile);
113 
114     d->actionPasteFromFile = ac->addAction (QStringLiteral("edit_paste_from_file"));
115     d->actionPasteFromFile->setText (i18n ("Paste &From File..."));
116     connect (d->actionPasteFromFile, &QAction::triggered, this, &kpMainWindow::slotPasteFromFile);
117 
118 
119     d->editMenuDocumentActionsEnabled = false;
120     enableEditMenuDocumentActions (false);
121 
122     // Paste should always be enabled, as long as there is something to paste
123     // (independent of whether we have a document or not)
124     connect (QApplication::clipboard(), &QClipboard::dataChanged,
125              this, &kpMainWindow::slotEnablePaste);
126 
127     slotEnablePaste ();
128 }
129 
130 //---------------------------------------------------------------------
131 
132 // private
enableEditMenuDocumentActions(bool enable)133 void kpMainWindow::enableEditMenuDocumentActions (bool enable)
134 {
135     // d->actionCut
136     // d->actionCopy
137     // d->actionPaste
138     // d->actionPasteInNewWindow
139 
140     // d->actionDelete
141 
142     d->actionSelectAll->setEnabled (enable);
143     // d->actionDeselect
144 
145     d->editMenuDocumentActionsEnabled = enable;
146 
147     // d->actionCopyToFile
148 
149     // Unlike d->actionPaste, we disable this if there is no document.
150     // This is because "File / Open" would do the same thing, if there is
151     // no document.
152     d->actionPasteFromFile->setEnabled (enable);
153 }
154 
155 //---------------------------------------------------------------------
156 
157 // public
selectionToolRMBMenu()158 QMenu *kpMainWindow::selectionToolRMBMenu ()
159 {
160     return qobject_cast <QMenu *> (guiFactory ()->container (QStringLiteral("selectionToolRMBMenu"), this));
161 }
162 
163 //---------------------------------------------------------------------
164 
165 // private slot
slotCut()166 void kpMainWindow::slotCut ()
167 {
168 #if DEBUG_KP_MAIN_WINDOW && 1
169     qCDebug(kpLogMainWindow) << "kpMainWindow::slotCut() CALLED";
170 #endif
171 
172     kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor);
173 
174     Q_ASSERT (d->document && d->document->selection ());
175 
176     toolEndShape ();
177 
178     slotCopy ();
179     slotDelete ();
180 }
181 
182 //---------------------------------------------------------------------
183 
NewTextMimeData(const QString & text)184 static QMimeData *NewTextMimeData (const QString &text)
185 {
186     auto *md = new QMimeData ();
187     md->setText (text);
188     return md;
189 }
190 
191 //---------------------------------------------------------------------
192 
193 // private slot
slotCopy()194 void kpMainWindow::slotCopy ()
195 {
196 #if DEBUG_KP_MAIN_WINDOW && 1
197     qCDebug(kpLogMainWindow) << "kpMainWindow::slotCopy() CALLED";
198 #endif
199 
200     kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor);
201 
202     Q_ASSERT (d->document && d->document->selection ());
203 
204     toolEndShape ();
205 
206     kpAbstractSelection *sel = d->document->selection ()->clone ();
207 
208     if (dynamic_cast <kpTextSelection *> (sel))
209     {
210         auto *textSel = dynamic_cast <kpTextSelection *> (sel);
211         if (!textSel->text ().isEmpty ())
212         {
213             QApplication::clipboard ()->setMimeData (
214                 ::NewTextMimeData (textSel->text ()),
215                 QClipboard::Clipboard);
216 
217             // SYNC: Normally, users highlight text and press CTRL+C.
218             //       Highlighting text copies it to the X11 "middle
219             //       mouse button" clipboard.  CTRL+C copies it to the
220             //       separate, Windows-like "CTRL+V" clipboard.
221             //
222             //       However, KolourPaint doesn't support highlighting.
223             //       So when they press CTRL+C to copy all text, simulate
224             //       the highlighting by copying the text to the "middle
225             //       mouse button" clipboard.  We don't do this for images
226             //       as no one ever middle-mouse-pastes images.
227             //
228             //       Note that we don't share the QMimeData pointer with
229             //       the above in case Qt doesn't expect it.
230             //
231             //       Once we change KolourPaint to support highlighted text
232             //       and CTRL+C to copy only the highlighted text, delete
233             //       this code.
234             QApplication::clipboard ()->setMimeData (
235                 ::NewTextMimeData (textSel->text ()),
236                 QClipboard::Selection);
237         }
238     }
239     else if (dynamic_cast <kpAbstractImageSelection *> (sel))
240     {
241         auto *imageSel = dynamic_cast <kpAbstractImageSelection *> (sel);
242 
243         // Transparency doesn't get sent across the aether so nuke it now
244         // so that transparency mask doesn't get needlessly recalculated
245         // if we ever call sel.setBaseImage().
246         imageSel->setTransparency (kpImageSelectionTransparency ());
247 
248         kpImage rawImage;
249 
250         if (imageSel->hasContent ()) {
251             rawImage = imageSel->baseImage ();
252         }
253         else {
254             rawImage = d->document->getSelectedBaseImage ();
255         }
256 
257         imageSel->setBaseImage ( rawImage );
258 
259         QApplication::clipboard ()->setMimeData (
260             new kpSelectionDrag (*imageSel),
261             QClipboard::Clipboard);
262     }
263     else {
264         Q_ASSERT (!"Unknown selection type");
265     }
266 
267     delete sel;
268 }
269 
270 //---------------------------------------------------------------------
271 
272 // private slot
slotEnablePaste()273 void kpMainWindow::slotEnablePaste ()
274 {
275     const QMimeData *md =
276         QApplication::clipboard()->mimeData(QClipboard::Clipboard);
277 
278     // It's faster to test for QMimeData::hasText() first due to the
279     // lazy evaluation of the '||' operator.
280     const bool shouldEnable = md && (md->hasText() || kpSelectionDrag::canDecode(md));
281 
282     d->actionPasteInNewWindow->setEnabled(shouldEnable);
283     d->actionPaste->setEnabled(shouldEnable);
284 }
285 
286 //---------------------------------------------------------------------
287 
288 // private
calcUsefulPasteRect(int imageWidth,int imageHeight)289 QRect kpMainWindow::calcUsefulPasteRect (int imageWidth, int imageHeight)
290 {
291 #if DEBUG_KP_MAIN_WINDOW && 1
292     qCDebug(kpLogMainWindow) << "kpMainWindow::calcUsefulPasteRect("
293                << imageWidth << "," << imageHeight
294                << ")";
295 #endif
296     Q_ASSERT (d->document);
297 
298     // TODO: 1st choice is to paste sel near but not overlapping last deselect point
299 
300     if (d->mainView && d->scrollView)
301     {
302         const QPoint viewTopLeft (d->scrollView->horizontalScrollBar()->value (),
303                                   d->scrollView->verticalScrollBar()->value ());
304 
305         const QPoint docTopLeft = d->mainView->transformViewToDoc (viewTopLeft);
306 
307         if ((docTopLeft.x () + imageWidth <= d->document->width () &&
308              docTopLeft.y () + imageHeight <= d->document->height ()) ||
309             imageWidth <= docTopLeft.x () ||
310             imageHeight <= docTopLeft.y ())
311         {
312             return  {docTopLeft.x (), docTopLeft.y (),  imageWidth, imageHeight};
313         }
314     }
315 
316     return  {0, 0, imageWidth, imageHeight};
317 }
318 
319 //---------------------------------------------------------------------
320 
321 // private
paste(const kpAbstractSelection & sel,bool forceTopLeft)322 void kpMainWindow::paste(const kpAbstractSelection &sel, bool forceTopLeft)
323 {
324 #if DEBUG_KP_MAIN_WINDOW && 1
325     qCDebug(kpLogMainWindow) << "kpMainWindow::paste(forceTopLeft=" << forceTopLeft << ")";
326 #endif
327 
328     kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor);
329 
330     toolEndShape ();
331 
332     //
333     // Make sure we've got a document (esp. with File/Close)
334     //
335 
336     if (!d->document)
337     {
338         auto *newDoc = new kpDocument (
339             sel.width (), sel.height (), documentEnvironment ());
340 
341         // will also create viewManager
342         setDocument (newDoc);
343     }
344 
345     //
346     // Paste as new selection
347     //
348 
349     const auto *imageSel = dynamic_cast <const kpAbstractImageSelection *> (&sel);
350 
351     if (imageSel && imageSel->hasContent () && imageSel->transparency ().isTransparent ())
352     {
353         d->colorToolBar->flashColorSimilarityToolBarItem ();
354     }
355 
356     kpAbstractSelection *selInUsefulPos = sel.clone ();
357     if (!forceTopLeft) {
358         selInUsefulPos->moveTo (calcUsefulPasteRect (sel.width (), sel.height ()).topLeft ());
359     }
360     // TODO: Should use kpCommandHistory::addCreateSelectionCommand(),
361     //       as well, to really support pasting selection borders.
362     addDeselectFirstCommand (new kpToolSelectionCreateCommand (
363         dynamic_cast <kpTextSelection *> (selInUsefulPos) ?
364             i18n ("Text: Create Box") :
365             i18n ("Selection: Create"),
366         *selInUsefulPos,
367         commandEnvironment ()));
368     delete selInUsefulPos;
369 
370 
371 #if DEBUG_KP_MAIN_WINDOW && 1
372     qCDebug(kpLogMainWindow) << "sel.size=" << QSize (sel.width (), sel.height ())
373                << " document.size="
374                << QSize (d->document->width (), d->document->height ());
375 #endif
376 
377     // If the selection is bigger than the document, automatically
378     // resize the document (with the option of Undo'ing) to fit
379     // the selection.
380     //
381     // No annoying dialog necessary.
382     //
383     if (sel.width () > d->document->width () ||
384         sel.height () > d->document->height ())
385     {
386         d->commandHistory->addCommand (
387             new kpTransformResizeScaleCommand (
388                 false/*act on doc, not sel*/,
389                 qMax (sel.width (), d->document->width ()),
390                 qMax (sel.height (), d->document->height ()),
391                 kpTransformResizeScaleCommand::Resize,
392                 commandEnvironment ()));
393     }
394 }
395 
396 //---------------------------------------------------------------------
397 
398 // public
pasteText(const QString & text,bool forceNewTextSelection,const QPoint & newTextSelectionTopLeft)399 void kpMainWindow::pasteText (const QString &text,
400                               bool forceNewTextSelection,
401                               const QPoint &newTextSelectionTopLeft)
402 {
403 #if DEBUG_KP_MAIN_WINDOW && 1
404     qCDebug(kpLogMainWindow) << "kpMainWindow::pasteText(" << text
405                << ",forceNewTextSelection=" << forceNewTextSelection
406                << ",newTextSelectionTopLeft=" << newTextSelectionTopLeft
407                << ")";
408 #endif
409 
410     if ( text.isEmpty() ) {
411         return;
412     }
413 
414     kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor);
415 
416     toolEndShape ();
417 
418     QStringList textLines = text.split('\n');
419 
420     if (!forceNewTextSelection &&
421         d->document && d->document->textSelection () &&
422         d->commandHistory && d->viewManager)
423     {
424     #if DEBUG_KP_MAIN_WINDOW && 1
425         qCDebug(kpLogMainWindow) << "\treusing existing Text Selection";
426     #endif
427 
428         d->viewManager->setQueueUpdates();
429 
430         kpTextSelection *textSel = d->document->textSelection ();
431         if (!textSel->hasContent ())
432         {
433         #if DEBUG_KP_MAIN_WINDOW && 1
434             qCDebug(kpLogMainWindow) << "\t\tneeds content";
435         #endif
436             commandHistory ()->addCreateSelectionCommand (
437                 new kpToolSelectionCreateCommand (
438                     i18n ("Text: Create Box"),
439                     *textSel,
440                     commandEnvironment ()),
441                 false/*no exec*/);
442         }
443 
444         kpMacroCommand *macroCmd = new kpMacroCommand (i18n ("Text: Paste"),
445             commandEnvironment ());
446         // (yes, this is the same check as the previous "if")
447         if (!textSel->hasContent ())
448         {
449             kpCommand *giveContentCmd = new kpToolTextGiveContentCommand (
450                 *textSel,
451                 QString ()/*uninteresting child of macro cmd*/,
452                 commandEnvironment ());
453             giveContentCmd->execute ();
454 
455             macroCmd->addCommand (giveContentCmd);
456         }
457 
458         for (int i = 0; i < textLines.size(); i++)
459         {
460             if (i > 0)
461             {
462                 macroCmd->addCommand (
463                     new kpToolTextEnterCommand (
464                         QString()/*uninteresting child of macroCmd*/,
465                         d->viewManager->textCursorRow (),
466                         d->viewManager->textCursorCol (),
467                         kpToolTextEnterCommand::AddEnterNow,
468                         commandEnvironment ()));
469             }
470 
471             macroCmd->addCommand (
472                 new kpToolTextInsertCommand (
473                     QString()/*uninteresting child of macroCmd*/,
474                     d->viewManager->textCursorRow (),
475                     d->viewManager->textCursorCol (),
476                     textLines [i],
477                     commandEnvironment ()));
478         }
479 
480         d->commandHistory->addCommand (macroCmd, false/*no exec*/);
481 
482         d->viewManager->restoreQueueUpdates();
483     }
484     else
485     {
486     #if DEBUG_KP_MAIN_WINDOW && 1
487         qCDebug(kpLogMainWindow) << "\tcreating Text Selection";
488     #endif
489 
490         const kpTextStyle ts = textStyle ();
491         const QFontMetrics fontMetrics = ts.fontMetrics ();
492 
493         int height = textLines.size () * fontMetrics.height ();
494         if (textLines.size () >= 1) {
495             height += (textLines.size () - 1) * fontMetrics.leading ();
496         }
497 
498         int width = 0;
499         foreach (const QString &str, textLines)
500           width = std::max(width, fontMetrics.horizontalAdvance(str));
501 
502         // limit the size to avoid memory overflow
503         width = qMin(qMax(QApplication::desktop()->width(), d->document ? d->document->width() : 0), width);
504         height = qMin(qMax(QApplication::desktop()->height(), d->document ? d->document->height() : 0), height);
505 
506         const int selWidth = qMax (kpTextSelection::MinimumWidthForTextStyle (ts),
507                                    width + kpTextSelection::TextBorderSize () * 2);
508         const int selHeight = qMax (kpTextSelection::MinimumHeightForTextStyle (ts),
509                                     height + kpTextSelection::TextBorderSize () * 2);
510         kpTextSelection newTextSel (QRect (0, 0, selWidth, selHeight),
511             textLines,
512             ts);
513 
514         if (newTextSelectionTopLeft != KP_INVALID_POINT)
515         {
516             newTextSel.moveTo (newTextSelectionTopLeft);
517             paste (newTextSel, true/*force topLeft*/);
518         }
519         else
520         {
521             paste (newTextSel);
522         }
523     }
524 }
525 
526 //---------------------------------------------------------------------
527 
528 // public
pasteTextAt(const QString & text,const QPoint & point,bool allowNewTextSelectionPointShift)529 void kpMainWindow::pasteTextAt (const QString &text, const QPoint &point,
530                                 bool allowNewTextSelectionPointShift)
531 {
532 #if DEBUG_KP_MAIN_WINDOW && 1
533     qCDebug(kpLogMainWindow) << "kpMainWindow::pasteTextAt(" << text
534                << ",point=" << point
535                << ",allowNewTextSelectionPointShift="
536                << allowNewTextSelectionPointShift
537                << ")";
538 #endif
539 
540     kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor);
541 
542     toolEndShape ();
543 
544 
545     if (d->document &&
546         d->document->textSelection () &&
547         d->document->textSelection ()->pointIsInTextArea (point))
548     {
549         kpTextSelection *textSel = d->document->textSelection ();
550 
551         int row, col;
552 
553         if (textSel->hasContent ())
554         {
555             row = textSel->closestTextRowForPoint (point);
556             col = textSel->closestTextColForPoint (point);
557         }
558         else
559         {
560             row = col = 0;
561         }
562 
563         d->viewManager->setTextCursorPosition (row, col);
564 
565         pasteText (text);
566     }
567     else
568     {
569         QPoint pointToUse = point;
570 
571         if (allowNewTextSelectionPointShift)
572         {
573             // TODO: In terms of doc pixels, would be inconsistent behaviour
574             //       based on zoomLevel of view.
575             // pointToUse -= QPoint (-view->selectionResizeHandleAtomicSize (),
576             //                       -view->selectionResizeHandleAtomicSize ());
577         }
578 
579         pasteText (text, true/*force new text selection*/, pointToUse);
580     }
581 }
582 
583 //---------------------------------------------------------------------
584 // public slot
585 
slotPaste()586 void kpMainWindow::slotPaste()
587 {
588     kpSetOverrideCursorSaver cursorSaver(Qt::WaitCursor);
589 
590     toolEndShape();
591 
592     const QMimeData *mimeData = QApplication::clipboard()->mimeData(QClipboard::Clipboard);
593 
594     kpAbstractImageSelection *sel = kpSelectionDrag::decode(mimeData);
595     if ( sel )
596     {
597         sel->setTransparency(imageSelectionTransparency());
598         paste(*sel);
599         delete sel;
600     }
601     else if ( mimeData->hasText() )
602     {
603         pasteText(mimeData->text());
604     }
605     else
606     {
607         kpSetOverrideCursorSaver cursorSaver(Qt::ArrowCursor);
608 
609         KMessageBox::sorry(this,
610             i18n("<qt>KolourPaint cannot paste the contents of"
611                  " the clipboard as it has an unknown format.</qt>"),
612             i18n("Cannot Paste"));
613     }
614 }
615 
616 //---------------------------------------------------------------------
617 
618 // private slot
slotPasteInNewWindow()619 void kpMainWindow::slotPasteInNewWindow ()
620 {
621 #if DEBUG_KP_MAIN_WINDOW && 1
622     qCDebug(kpLogMainWindow) << "kpMainWindow::slotPasteInNewWindow() CALLED";
623 #endif
624 
625     kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor);
626 
627     toolEndShape ();
628 
629     //
630     // Pasting must ensure that:
631     //
632     // Requirement 1. the document is the same size as the image to be pasted.
633     // Requirement 2. transparent pixels in the image must remain as transparent.
634     //
635 
636     auto *win = new kpMainWindow (nullptr/*no document*/);
637     win->show ();
638 
639     // Make "Edit / Paste in New Window" always paste white pixels as white.
640     // Don't let selection transparency get in the way and paste them as
641     // transparent.
642     kpImageSelectionTransparency transparency = win->imageSelectionTransparency ();
643     if (transparency.isTransparent ())
644     {
645     #if DEBUG_KP_MAIN_WINDOW && 1
646         qCDebug(kpLogMainWindow) << "\tchanging image selection transparency to opaque";
647     #endif
648         transparency.setOpaque ();
649         // Since we are setting selection transparency programmatically
650         // -- as opposed to in response to user input -- this will not
651         // affect the selection transparency tool option widget's "last used"
652         // config setting.
653         win->setImageSelectionTransparency (transparency);
654     }
655 
656     // (this handles Requirement 1. above)
657     win->slotPaste ();
658 
659     // if slotPaste could not decode clipboard data, no document was created
660     if ( win->document() )
661     {
662       // (this handles Requirement 2. above;
663       //  slotDeselect() is not enough unless the document is filled with the
664       //  transparent color in advance)
665       win->slotCrop();
666     }
667 }
668 
669 //---------------------------------------------------------------------
670 
671 // public slot
slotDelete()672 void kpMainWindow::slotDelete ()
673 {
674 #if DEBUG_KP_MAIN_WINDOW && 1
675     qCDebug(kpLogMainWindow) << "kpMainWindow::slotDelete() CALLED";
676 #endif
677     if (!d->actionDelete->isEnabled ())
678     {
679     #if DEBUG_KP_MAIN_WINDOW && 1
680         qCDebug(kpLogMainWindow) << "\taction not enabled - was probably called from kpTool::keyPressEvent()";
681     #endif
682         return;
683     }
684 
685     Q_ASSERT (d->document && d->document->selection ());
686 
687     toolEndShape ();
688 
689     addImageOrSelectionCommand (new kpToolSelectionDestroyCommand (
690         d->document->textSelection () ?
691             i18n ("Text: Delete Box") :  // not to be confused with i18n ("Text: Delete")
692             i18n ("Selection: Delete"),
693         false/*no push onto doc*/,
694         commandEnvironment ()));
695 }
696 
697 //---------------------------------------------------------------------
698 
699 // private slot
slotSelectAll()700 void kpMainWindow::slotSelectAll ()
701 {
702 #if DEBUG_KP_MAIN_WINDOW && 1
703     qCDebug(kpLogMainWindow) << "kpMainWindow::slotSelectAll() CALLED";
704 #endif
705     Q_ASSERT (d->document);
706 
707     toolEndShape ();
708 
709     if (d->document->selection ()) {
710         slotDeselect ();
711     }
712 
713     // just the border - don't actually pull image from doc yet
714     d->document->setSelection (
715         kpRectangularImageSelection (d->document->rect (),
716             imageSelectionTransparency ()));
717 
718     if (tool ()) {
719         tool ()->somethingBelowTheCursorChanged ();
720     }
721 }
722 
723 //---------------------------------------------------------------------
724 
725 // private
addDeselectFirstCommand(kpCommand * cmd)726 void kpMainWindow::addDeselectFirstCommand (kpCommand *cmd)
727 {
728 #if DEBUG_KP_MAIN_WINDOW && 1
729     qCDebug(kpLogMainWindow) << "kpMainWindow::addDeselectFirstCommand("
730                << cmd
731                << ")";
732 #endif
733 
734 
735     kpAbstractSelection *sel = d->document->selection ();
736 
737 #if DEBUG_KP_MAIN_WINDOW && 1
738     qCDebug(kpLogMainWindow) << "\tsel=" << sel;
739 #endif
740 
741     if (sel)
742     {
743         // if you just dragged out something with no action then
744         // forget the drag
745         if (!sel->hasContent ())
746         {
747         #if DEBUG_KP_MAIN_WINDOW && 1
748             qCDebug(kpLogMainWindow) << "\tjust a fresh border - was nop - delete";
749         #endif
750             d->document->selectionDelete ();
751             if (tool ()) {
752                 tool ()->somethingBelowTheCursorChanged ();
753             }
754 
755             if (cmd) {
756                 d->commandHistory->addCommand (cmd);
757             }
758         }
759         else
760         {
761         #if DEBUG_KP_MAIN_WINDOW && 1
762             qCDebug(kpLogMainWindow) << "\treal selection with image - push onto doc cmd";
763         #endif
764             kpCommand *deselectCommand = new kpToolSelectionDestroyCommand (
765                 dynamic_cast <kpTextSelection *> (sel) ?
766                     i18n ("Text: Finish") :
767                     i18n ("Selection: Deselect"),
768                 true/*push onto document*/,
769                 commandEnvironment ());
770 
771             if (cmd)
772             {
773                 kpMacroCommand *macroCmd = new kpMacroCommand (cmd->name (),
774                     commandEnvironment ());
775                 macroCmd->addCommand (deselectCommand);
776                 macroCmd->addCommand (cmd);
777                 d->commandHistory->addCommand (macroCmd);
778             }
779             else {
780                 d->commandHistory->addCommand (deselectCommand);
781             }
782         }
783     }
784     else
785     {
786         if (cmd) {
787             d->commandHistory->addCommand (cmd);
788         }
789     }
790 }
791 
792 //---------------------------------------------------------------------
793 
794 // public slot
slotDeselect()795 void kpMainWindow::slotDeselect ()
796 {
797 #if DEBUG_KP_MAIN_WINDOW && 1
798     qCDebug(kpLogMainWindow) << "kpMainWindow::slotDeselect() CALLED";
799 #endif
800     Q_ASSERT (d->document && d->document->selection ());
801 
802     toolEndShape ();
803 
804     addDeselectFirstCommand (nullptr);
805 }
806 
807 //---------------------------------------------------------------------
808 
809 // private slot
slotCopyToFile()810 void kpMainWindow::slotCopyToFile ()
811 {
812 #if DEBUG_KP_MAIN_WINDOW
813     qCDebug(kpLogMainWindow) << "kpMainWindow::slotCopyToFile()";
814 #endif
815 
816     toolEndShape ();
817 
818 
819     if (!d->document->selection ()) {
820         return;
821     }
822 
823     kpImage imageToSave;
824 
825     if (d->document->imageSelection ())
826     {
827         kpAbstractImageSelection *imageSel = d->document->imageSelection ();
828         if (!imageSel->hasContent ())
829         {
830             // Not a floating selection - user has just selected a region;
831             // haven't pulled it off yet so probably don't expect and can't
832             // visualize selection transparency so give opaque, not transparent
833             // image.
834             imageToSave = d->document->getSelectedBaseImage ();
835         }
836         else {
837             imageToSave = imageSel->transparentImage ();
838         }
839     }
840     else if (d->document->textSelection ())
841     {
842         imageToSave = d->document->textSelection ()->approximateImage ();
843     }
844     else {
845         Q_ASSERT (!"Unknown selection type");
846     }
847 
848 
849     kpDocumentSaveOptions chosenSaveOptions;
850     bool allowLossyPrompt;
851     QUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Copy to File"),
852                                     d->lastCopyToURL.url (),
853                                     imageToSave,
854                                     d->lastCopyToSaveOptions,
855                                     kpDocumentMetaInfo (),
856                                     kpSettingsGroupEditCopyTo,
857                                     false/*allow remote files*/,
858                                     &chosenSaveOptions,
859                                     d->copyToFirstTime,
860                                     &allowLossyPrompt);
861 
862     if (chosenURL.isEmpty ()) {
863         return;
864     }
865 
866 
867     if (!kpDocument::savePixmapToFile (imageToSave,
868                                        chosenURL,
869                                        chosenSaveOptions, kpDocumentMetaInfo (),
870                                        allowLossyPrompt,
871                                        this))
872     {
873         return;
874     }
875 
876 
877     addRecentURL (chosenURL);
878 
879 
880     d->lastCopyToURL = chosenURL;
881     d->lastCopyToSaveOptions = chosenSaveOptions;
882 
883     d->copyToFirstTime = false;
884 }
885 
886 //---------------------------------------------------------------------
887 
888 // private slot
slotPasteFromFile()889 void kpMainWindow::slotPasteFromFile ()
890 {
891 #if DEBUG_KP_MAIN_WINDOW
892     qCDebug(kpLogMainWindow) << "kpMainWindow::slotPasteFromFile()";
893 #endif
894 
895     toolEndShape ();
896 
897 
898     QList<QUrl> urls = askForOpenURLs(i18nc ("@title:window", "Paste From File"),
899                                      false/*only 1 URL*/);
900 
901     if (urls.count () != 1) {
902         return;
903     }
904 
905     QUrl url = urls.first ();
906 
907     kpImage image = kpDocument::getPixmapFromFile (url,
908         false/*show error message if doesn't exist*/,
909         this);
910 
911     if (image.isNull ()) {
912         return;
913     }
914 
915     addRecentURL (url);
916 
917     paste (kpRectangularImageSelection (
918         QRect (0, 0, image.width (), image.height ()),
919         image,
920         imageSelectionTransparency ()));
921 }
922 
923 //---------------------------------------------------------------------
924