View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0017966 | Scribus | Usability | public | 2026-09-05 10:19 | 2026-09-05 12:16 |
| Reporter | buttar | Assigned To | |||
| Priority | normal | Severity | feature | Reproducibility | N/A |
| Status | new | Resolution | open | ||
| Platform | Linux | OS | Fedora Workstation | OS Version | 44 |
| Product Version | 1.7.4.svn | ||||
| Summary | 0017966: [patch] A Docakable Images Palette | ||||
| Description | I don't know if this is the right way to propose a new feature but this is the only one I know. I hope it adds value to Scribus. A new core, dockable Images palette that consolidates image-management workflows into one modeless interface. If mature enough, it could eventually replace or absorb much of: * Manage Images — document image listing, status, relinking and related actions. * Picture Browser — filesystem browsing, placement and collections, planned for later stages. Existing image properties, effects and external-editor workflows are reused rather than duplicated. The current patches do not remove either legacy interface. ## IMG1 — `img1-images-palette-foundation-v1.02.patch` * Adds the core Images dock palette. * Introduces focused `ImagePalette` and `ImagePaletteDocumentModel` classes. * Adds Document and Current Page scopes. * Shows thumbnails, filename/path, page/frame location and basic metadata. * Follows document lifecycle, page changes and canvas selection. * Includes applied-master-page images where appropriate. * Uses guarded pointers, lazy hidden-palette updates and coalesced refresh. * Establishes the model/view foundation for later image-management scopes. * Leaves Manage Images and Picture Browser unchanged. ## IMG2 — `img2-images-document-management-v1.30.patch` * Applies after IMG1 v1.02 and adds document-image management functionality. * Adds Missing (n) scope and missing-image tracking. * Adds multi-placement navigation with page/frame identification. * Adds Show and Export/Print controls. * Adds actions: -- Select -- Replace… -- Find… -- Location -- Edit… -- Effects… -- Properties * Reuses existing Scribus relinking, missing-image search, editor, effects and properties workflows. * Adds metadata: -- Format -- Color Space -- Pixel Size -- Native DPI -- Effective DPI -- Scale -- Frame Size -- Scaled Image Size * Keeps canvas → palette synchronization automatic; palette → canvas selection remains explicit. * Preserves palette selection across normal document refreshes. * Uses a shared inspector label/value rail and natural Qt layout spacing. * Elides long text safely during resizing. * Enforces a structural minimum width so the inspector cannot collapse into overlap. * Moves toward Manage Images parity while leaving both legacy interfaces available for now. ## IMG3 — Images Folder Browsing * Depends on / applies after: IMG1 `img1-images-palette-foundation-v1.03.patch` and IMG2 `img2-images-document-management-v1.33.patch`. * Adds a new Browse mode beside the existing Document mode. * Browses supported image/graphic files directly from a folder, including preview-capable formats such as PDF and SVG. * Loads folder contents and thumbnails asynchronously via `ImagePaletteFileWorker`. * Uses generation-based cancellation so stale results are ignored during rapid folder changes. * Shows file metadata including format, color space, pixel size, native DPI, name, and path. * Remembers the last browsed folder and falls back to the document/Documents/home directory as needed. * Keeps Document-mode refresh logic inactive while Browse mode is selected. * Adds the required Browse/model/worker sources to the build. * v1.02 adds the direct `cmsettings.h` include required by the thumbnail worker. ### IMG4 — Browse Drag & Drop * Depends on / applies after: IMG1 `img1-images-palette-foundation-v1.03.patch`, IMG2 `img2-images-document-management-v1.33.patch`, and IMG3 `img3-images-folder-browsing-v1.04.patch`. * Adds multi-selection to Browse mode with `ExtendedSelection`. * Enables dragging one or more selected files out as standard `text/uri-list` data using `CopyAction`. * Reuses Scribus’s existing canvas/file-drop handling; no new placement engine or canvas drop logic is added. * Adds a multi-selection inspector state: -- `N files selected` -- shared folder path -- per-file metadata cleared to `—` * Keeps inspector geometry stable when switching between single and multiple selection. * Adds a stable minimum width for the Browse inspector so the inner splitter cannot be dragged far enough to clip metadata. * Single PDF/SVG drops continue through Scribus’s existing single-file import path; existing multi-vector behavior is unchanged. | ||||
| Additional Information | A series of 4 patches, and some screenshots are attached. | ||||
| Tags | #please_test | ||||
| Attached Files | img1-images-palette-foundation-v1.03.patch (28,018 bytes)
Index: scribus/CMakeLists_Sources.txt
===================================================================
--- scribus/CMakeLists_Sources.txt (revision 27814)
+++ scribus/CMakeLists_Sources.txt (working copy)
@@ -366,6 +366,8 @@
ui/imageclippingpaths.cpp
ui/imagelayers.cpp
ui/imageinfodialog.cpp
+ ui/imagepalette.cpp
+ ui/imagepalette_documentmodel.cpp
ui/inlinepalette.cpp
ui/insertTable.cpp
ui/insertaframe.cpp
Index: scribus/actionmanager.cpp
===================================================================
--- scribus/actionmanager.cpp (revision 27814)
+++ scribus/actionmanager.cpp (working copy)
@@ -848,6 +848,8 @@
scrActions->insert(name, new ScrAction("", defaultKey(name), mainWindow));
name="toolsContent";
scrActions->insert(name, new ScrAction("", defaultKey(name), mainWindow));
+ name = "toolsImages";
+ scrActions->insert(name, new ScrAction("", defaultKey(name), mainWindow));
name = "toolsOutline";
scrActions->insert(name, new ScrAction("", defaultKey(name), mainWindow));
name = "toolsScrapbook";
@@ -1782,6 +1784,7 @@
//Tool menu
(*scrActions)["toolsProperties"]->setTexts( tr("&Properties"));
(*scrActions)["toolsContent"]->setTexts( tr("Content Properties"));
+ (*scrActions)["toolsImages"]->setTexts( tr("&Images"));
(*scrActions)["toolsOutline"]->setTexts( tr("&Outline", "Document Outline Palette"));
(*scrActions)["toolsScrapbook"]->setTexts( tr("&Scrapbook"));
(*scrActions)["toolsLayers"]->setTexts( tr("&Layers"));
@@ -2488,6 +2491,7 @@
<< "specialToggleAllPalettes"
<< "toolsProperties"
<< "toolsContent"
+ << "toolsImages"
<< "toolsOutline"
<< "toolsScrapbook"
<< "toolsLayers"
Index: scribus/manager/dock_manager.cpp
===================================================================
--- scribus/manager/dock_manager.cpp (revision 27814)
+++ scribus/manager/dock_manager.cpp (working copy)
@@ -35,6 +35,7 @@
#include "ui/aligndistribute.h"
#include "ui/bookmarkpalette.h"
#include "ui/contentpalette.h"
+#include "ui/imagepalette.h"
#include "ui/inlinepalette.h"
#include "ui/layers.h"
#include "ui/outlinepalette.h"
@@ -68,6 +69,7 @@
alignDistributePalette = new AlignDistributePalette();
bookPalette = new BookPalette(this);
contentPalette = new ContentPalette(this);
+ imagePalette = new ImagePalette(this);
inlinePalette = new InlinePalette(this);
layerPalette = new LayerPalette(this);
outlinePalette = new OutlinePalette(this);
@@ -81,6 +83,7 @@
configureDock(alignDistributePalette);
configureDock(bookPalette);
configureDock(contentPalette);
+ configureDock(imagePalette);
configureDock(inlinePalette);
configureDock(layerPalette);
configureDock(outlinePalette);
@@ -268,6 +271,7 @@
addDockWidgetTabToArea(scrapbookPalette, areaCenterLeft);
addDockWidgetTabToArea(bookPalette, areaCenterLeft);
addDockWidgetTabToArea(symbolPalette, areaCenterLeft);
+ addDockWidgetTabToArea(imagePalette, areaCenterLeft);
// Left Bottom
auto *areaLeftBottom = addDockWidget(BottomDockWidgetArea, layerPalette, areaLeft);
@@ -295,6 +299,7 @@
// hide panels that are not visible in default workspace
inlinePalette->closeDockWidget();
+ imagePalette->closeDockWidget();
scrapbookPalette->closeDockWidget();
bookPalette->closeDockWidget();
symbolPalette->closeDockWidget();
Index: scribus/manager/dock_manager.h
===================================================================
--- scribus/manager/dock_manager.h (revision 27814)
+++ scribus/manager/dock_manager.h (working copy)
@@ -37,6 +37,7 @@
class OutlinePalette;
class PropertiesPalette;
class ContentPalette;
+class ImagePalette;
class LayerPalette;
class AlignDistributePalette;
class InlinePalette;
@@ -88,6 +89,7 @@
OutlinePalette *outlinePalette {nullptr};
PropertiesPalette *propertiesPalette {nullptr};
ContentPalette *contentPalette {nullptr};
+ ImagePalette *imagePalette {nullptr};
LayerPalette *layerPalette {nullptr};
AlignDistributePalette *alignDistributePalette {nullptr};
InlinePalette *inlinePalette {nullptr};
Index: scribus/scribus.cpp
===================================================================
--- scribus/scribus.cpp (revision 27814)
+++ scribus/scribus.cpp (working copy)
@@ -167,6 +167,7 @@
#include "ui/collectforoutput_ui.h"
#include "ui/colorpicker/colorpicker.h"
#include "ui/contentpalette.h"
+#include "ui/imagepalette.h"
#include "ui/contextmenu.h"
#include "ui/copypagetomasterpagedialog.h"
#include "ui/customfdialog.h"
@@ -730,6 +731,12 @@
contentPalette->setToggleViewAction(scrActions["toolsContent"]);
contentPalette->installEventFilter(this);
+ // Images
+ imagePalette = dockManager->imagePalette;
+ imagePalette->setMainWindow(this);
+ imagePalette->setToggleViewAction(scrActions["toolsImages"]);
+ imagePalette->installEventFilter(this);
+
// Nodes
nodePalette = new NodePalette(this);
nodePalette->installEventFilter(this);
@@ -1405,6 +1412,7 @@
scrMenuMgr->addMenuItemString("SEPARATOR", "Windows");
scrMenuMgr->addMenuItemString("toolsProperties", "Windows");
scrMenuMgr->addMenuItemString("toolsContent", "Windows");
+ scrMenuMgr->addMenuItemString("toolsImages", "Windows");
scrMenuMgr->addMenuItemString("toolsActionHistory", "Windows");
scrMenuMgr->addMenuItemString("toolsAlignDistribute", "Windows");
scrMenuMgr->addMenuItemString("SEPARATOR", "Windows");
@@ -2609,6 +2617,7 @@
updateActiveWindowCaption(doc->documentFileName());
propertiesPalette->setDoc(doc);
contentPalette->setDoc(doc);
+ imagePalette->setDoc(doc);
marksManager->setDoc(doc);
nsEditor->setDoc(doc);
pagePalette->setView(view);
@@ -2658,6 +2667,7 @@
updateActiveWindowCaption(doc->documentFileName());
propertiesPalette->setDoc(doc);
contentPalette->setDoc(doc);
+ imagePalette->setDoc(doc);
nsEditor->setDoc(doc);
marksManager->setDoc(doc);
@@ -4225,6 +4235,7 @@
//<<Palettes
propertiesPalette->unsetDoc();
contentPalette->unsetDoc();
+ imagePalette->unsetDoc();
inlinePalette->unsetDoc();
symbolPalette->unsetDoc();
pagePalette->setView(nullptr);
@@ -4919,6 +4930,8 @@
propertiesPalette->setDoc(doc);
contentPalette->unsetDoc();
contentPalette->setDoc(doc);
+ imagePalette->unsetDoc();
+ imagePalette->setDoc(doc);
marksManager->setDoc(doc);
nsEditor->setDoc(doc);
symbolPalette->unsetDoc();
@@ -6780,6 +6793,7 @@
{
propertiesPalette->startup();
contentPalette->startup();
+ imagePalette->startup();
outlinePalette->startup();
scrapbookPalette->startup();
bookmarkPalette->startup();
@@ -8281,6 +8295,7 @@
pageSelector->setMaximum(doc->masterPageMode() ? 1 : doc->Pages->count());
if ((!doc->isLoading()) && (!doc->masterPageMode()))
pageSelector->setGUIForPage(pageIndex);
+ emit currentPageChanged(pageIndex);
}
void ScribusMainWindow::setCurrentPage(int p)
Index: scribus/scribus.h
===================================================================
--- scribus/scribus.h (revision 27814)
+++ scribus/scribus.h (working copy)
@@ -82,6 +82,7 @@
class FormatsManager;
class GuideManager;
class HelpBrowser;
+class ImagePalette;
class InlinePalette;
class LayerPalette;
class MarksManager;
@@ -258,6 +259,7 @@
CharSelect *charPalette {nullptr};
PropertiesPalette *propertiesPalette {nullptr};
ContentPalette *contentPalette {nullptr};
+ ImagePalette *imagePalette {nullptr};
MarksManager *marksManager {nullptr};
NotesStylesEditor *nsEditor {nullptr};
NodePalette *nodePalette {nullptr};
@@ -621,6 +623,7 @@
void TextEffects(int);
void UpdateRequest(int updateFlags);
void changeLayers(int);
+ void currentPageChanged(int pageIndex);
protected:
/*!
Index: scribus/ui/imagepalette.cpp
===================================================================
--- scribus/ui/imagepalette.cpp (nonexistent)
+++ scribus/ui/imagepalette.cpp (working copy)
@@ -0,0 +1,396 @@
+/*
+For general Scribus (>=1.3.2) copyright and licensing information please refer
+to the COPYING file provided with the program. Following this notice may exist
+a copyright and/or license notice that predates the release of Scribus 1.3.2
+for which a new license (GPL+exception) is in place.
+*/
+
+#include "imagepalette.h"
+
+#include <QAbstractItemView>
+#include <QDir>
+#include <QEvent>
+#include <QFileInfo>
+#include <QFormLayout>
+#include <QFrame>
+#include <QItemSelectionModel>
+#include <QLabel>
+#include <QListView>
+#include <QSignalBlocker>
+#include <QSplitter>
+#include <QTabBar>
+#include <QTimer>
+#include <QVBoxLayout>
+
+#include "imagepalette_documentmodel.h"
+#include "pageitem.h"
+#include "selection.h"
+#include "scribus.h"
+#include "scribusdoc.h"
+
+ImagePalette::ImagePalette(QWidget* parent)
+ : DockPanelBase("Images", "image-collection", parent),
+ m_model(new ImagePaletteDocumentModel(this)),
+ m_refreshTimer(new QTimer(this))
+{
+ setObjectName(QStringLiteral("ImagePalette"));
+
+ auto* panel = new QWidget(this);
+ auto* layout = new QVBoxLayout(panel);
+ layout->setContentsMargins(6, 6, 6, 6);
+ layout->setSpacing(6);
+
+ m_scopeTabs = new QTabBar(panel);
+ m_scopeTabs->setDocumentMode(true);
+ m_scopeTabs->setExpanding(false);
+ m_scopeTabs->addTab(QString());
+ m_scopeTabs->addTab(QString());
+ layout->addWidget(m_scopeTabs);
+
+ auto* splitter = new QSplitter(Qt::Horizontal, panel);
+ splitter->setChildrenCollapsible(false);
+ layout->addWidget(splitter, 1);
+
+ m_imageView = new QListView(splitter);
+ m_imageView->setMinimumSize(220, 180);
+ m_imageView->setModel(m_model);
+ m_imageView->setIconSize(QSize(128, 128));
+ m_imageView->setGridSize(QSize(156, 174));
+ m_imageView->setResizeMode(QListView::Adjust);
+ m_imageView->setViewMode(QListView::IconMode);
+ m_imageView->setMovement(QListView::Static);
+ m_imageView->setFlow(QListView::LeftToRight);
+ m_imageView->setWrapping(true);
+ m_imageView->setWordWrap(true);
+ m_imageView->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ m_imageView->setSelectionMode(QAbstractItemView::SingleSelection);
+
+ auto* inspector = new QWidget(splitter);
+ inspector->setMinimumWidth(210);
+ auto* inspectorLayout = new QVBoxLayout(inspector);
+ inspectorLayout->setContentsMargins(8, 0, 0, 0);
+
+ m_nameValue = new QLabel(inspector);
+ QFont nameFont = m_nameValue->font();
+ nameFont.setBold(true);
+ m_nameValue->setFont(nameFont);
+ m_nameValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ inspectorLayout->addWidget(m_nameValue);
+
+ auto* line = new QFrame(inspector);
+ line->setFrameShape(QFrame::HLine);
+ line->setFrameShadow(QFrame::Sunken);
+ inspectorLayout->addWidget(line);
+
+ auto* form = new QFormLayout();
+ form->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
+ form->setLabelAlignment(Qt::AlignLeft | Qt::AlignTop);
+ inspectorLayout->addLayout(form);
+
+ m_pathLabel = new QLabel(inspector);
+ m_pathValue = new QLabel(inspector);
+ m_pathValue->setWordWrap(true);
+ m_pathValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ form->addRow(m_pathLabel, m_pathValue);
+
+ m_pageLabel = new QLabel(inspector);
+ m_pageValue = new QLabel(inspector);
+ m_pageValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ form->addRow(m_pageLabel, m_pageValue);
+
+ m_frameLabel = new QLabel(inspector);
+ m_frameValue = new QLabel(inspector);
+ m_frameValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ form->addRow(m_frameLabel, m_frameValue);
+
+ m_dimensionsLabel = new QLabel(inspector);
+ m_dimensionsValue = new QLabel(inspector);
+ m_dimensionsValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ form->addRow(m_dimensionsLabel, m_dimensionsValue);
+
+ m_resolutionLabel = new QLabel(inspector);
+ m_resolutionValue = new QLabel(inspector);
+ m_resolutionValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ form->addRow(m_resolutionLabel, m_resolutionValue);
+
+ m_statusLabel = new QLabel(inspector);
+ m_statusValue = new QLabel(inspector);
+ m_statusValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ form->addRow(m_statusLabel, m_statusValue);
+
+ inspectorLayout->addStretch(1);
+ splitter->setStretchFactor(0, 3);
+ splitter->setStretchFactor(1, 2);
+
+ m_summaryLabel = new QLabel(panel);
+ layout->addWidget(m_summaryLabel);
+
+ setWidget(panel);
+
+ m_refreshTimer->setSingleShot(true);
+ m_refreshTimer->setInterval(75);
+
+ connect(m_scopeTabs, &QTabBar::currentChanged, this, &ImagePalette::scopeChanged);
+ connect(m_imageView->selectionModel(), &QItemSelectionModel::selectionChanged,
+ this, &ImagePalette::handleViewSelectionChanged);
+ connect(m_refreshTimer, &QTimer::timeout, this, &ImagePalette::rebuild);
+ connect(this, &DockPanelBase::visibilityChanged, this, &ImagePalette::handleVisibilityChanged);
+
+ languageChange();
+ clearInspector();
+ updateSummary();
+}
+
+void ImagePalette::setMainWindow(ScribusMainWindow* mw)
+{
+ if (m_ScMW == mw)
+ return;
+
+ if (m_ScMW)
+ disconnect(m_ScMW, &ScribusMainWindow::currentPageChanged, this, &ImagePalette::handleCurrentPageChanged);
+
+ m_ScMW = mw;
+ if (m_ScMW)
+ connect(m_ScMW, &ScribusMainWindow::currentPageChanged, this, &ImagePalette::handleCurrentPageChanged);
+}
+
+void ImagePalette::setDoc(ScribusDoc* doc)
+{
+ if (doc == static_cast<ScribusDoc*>(m_doc))
+ {
+ m_dirty = true;
+ if (isVisible())
+ rebuild();
+ return;
+ }
+
+ if (m_doc)
+ {
+ disconnect(m_doc, &ScribusDoc::docChanged, this, &ImagePalette::handleDocumentChanged);
+ disconnect(m_doc->m_Selection, &Selection::selectionChanged, this, &ImagePalette::handleDocumentSelectionChanged);
+ }
+
+ m_doc = doc;
+ m_model->setDocument(m_doc);
+
+ if (m_doc)
+ {
+ connect(m_doc, &ScribusDoc::docChanged, this, &ImagePalette::handleDocumentChanged);
+ connect(m_doc->m_Selection, &Selection::selectionChanged, this, &ImagePalette::handleDocumentSelectionChanged);
+ }
+
+ m_dirty = true;
+ if (isVisible())
+ rebuild();
+ else
+ updateSummary();
+}
+
+void ImagePalette::unsetDoc()
+{
+ if (m_doc)
+ {
+ disconnect(m_doc, &ScribusDoc::docChanged, this, &ImagePalette::handleDocumentChanged);
+ disconnect(m_doc->m_Selection, &Selection::selectionChanged, this, &ImagePalette::handleDocumentSelectionChanged);
+ }
+
+ m_refreshTimer->stop();
+ m_doc = nullptr;
+ m_model->setDocument(nullptr);
+ m_dirty = false;
+ clearInspector();
+ updateSummary();
+}
+
+void ImagePalette::changeEvent(QEvent* event)
+{
+ DockPanelBase::changeEvent(event);
+ if (event->type() == QEvent::LanguageChange)
+ languageChange();
+}
+
+void ImagePalette::scopeChanged(int index)
+{
+ m_model->setCurrentPageOnly(index == 1);
+ rebuild();
+}
+
+void ImagePalette::handleDocumentChanged()
+{
+ m_dirty = true;
+ scheduleRebuild();
+}
+
+void ImagePalette::handleDocumentSelectionChanged()
+{
+ if (m_scopeTabs->currentIndex() == 1)
+ {
+ m_dirty = true;
+ scheduleRebuild();
+ return;
+ }
+
+ syncSelectionFromDocument();
+ updateSummary();
+}
+
+void ImagePalette::handleCurrentPageChanged()
+{
+ if (m_scopeTabs->currentIndex() != 1)
+ return;
+
+ m_dirty = true;
+ scheduleRebuild();
+}
+
+void ImagePalette::handleVisibilityChanged(bool visible)
+{
+ if (!visible || !m_dirty)
+ return;
+
+ m_refreshTimer->stop();
+ rebuild();
+}
+
+void ImagePalette::handleViewSelectionChanged(const QItemSelection&, const QItemSelection&)
+{
+ showItem(m_model->itemAt(m_imageView->currentIndex()));
+}
+
+void ImagePalette::clearInspector()
+{
+ const QString empty = QString::fromUtf8("—");
+ m_nameValue->setText(empty);
+ m_pathValue->setText(empty);
+ m_pageValue->setText(empty);
+ m_frameValue->setText(empty);
+ m_dimensionsValue->setText(empty);
+ m_resolutionValue->setText(empty);
+ m_statusValue->setText(empty);
+}
+
+void ImagePalette::languageChange()
+{
+ setWindowTitle(tr("Images"));
+ m_scopeTabs->setTabText(0, tr("Document"));
+ m_scopeTabs->setTabText(1, tr("Current Page"));
+ m_pathLabel->setText(tr("Path:"));
+ m_pageLabel->setText(tr("Page:"));
+ m_frameLabel->setText(tr("Frame:"));
+ m_dimensionsLabel->setText(tr("Dimensions:"));
+ m_resolutionLabel->setText(tr("Effective:"));
+ m_statusLabel->setText(tr("Status:"));
+ m_dirty = true;
+ if (isVisible())
+ rebuild();
+ else
+ updateSummary();
+}
+
+void ImagePalette::rebuild()
+{
+ m_refreshTimer->stop();
+ m_model->rebuild();
+ m_dirty = false;
+ syncSelectionFromDocument();
+ updateSummary();
+
+ if (!m_imageView->currentIndex().isValid())
+ clearInspector();
+}
+
+void ImagePalette::scheduleRebuild()
+{
+ if (!isVisible())
+ return;
+ m_refreshTimer->start();
+}
+
+void ImagePalette::showItem(PageItem* item)
+{
+ if (!item)
+ {
+ clearInspector();
+ return;
+ }
+
+ QFileInfo fileInfo(item->Pfile);
+ if (item->isInlineImage)
+ {
+ m_nameValue->setText(tr("Embedded Image"));
+ m_pathValue->setText(tr("Embedded in document"));
+ }
+ else if (item->Pfile.isEmpty())
+ {
+ m_nameValue->setText(tr("Empty Image Frame"));
+ m_pathValue->setText(QString::fromUtf8("—"));
+ }
+ else
+ {
+ m_nameValue->setText(fileInfo.fileName());
+ m_pathValue->setText(QDir::toNativeSeparators(item->Pfile));
+ }
+
+ if (!item->OnMasterPage.isEmpty())
+ m_pageValue->setText(tr("Master: %1").arg(item->OnMasterPage));
+ else if (item->OwnPage < 0)
+ m_pageValue->setText(tr("Not on a Page"));
+ else
+ m_pageValue->setText(QString::number(item->OwnPage + 1));
+
+ m_frameValue->setText(item->itemName());
+
+ if (item->imageIsAvailable)
+ {
+ m_dimensionsValue->setText(tr("%1 × %2 px").arg(item->OrigW).arg(item->OrigH));
+ if (item->imageXScale() != 0.0 && item->imageYScale() != 0.0)
+ {
+ m_resolutionValue->setText(tr("%1 × %2 dpi")
+ .arg(qRound(72.0 / item->imageXScale()))
+ .arg(qRound(72.0 / item->imageYScale())));
+ }
+ else
+ m_resolutionValue->setText(QString::fromUtf8("—"));
+ }
+ else
+ {
+ m_dimensionsValue->setText(QString::fromUtf8("—"));
+ m_resolutionValue->setText(QString::fromUtf8("—"));
+ }
+
+ if (item->isInlineImage)
+ m_statusValue->setText(tr("Embedded"));
+ else if (item->Pfile.isEmpty())
+ m_statusValue->setText(tr("Empty"));
+ else if (item->imageIsAvailable)
+ m_statusValue->setText(tr("Linked"));
+ else
+ m_statusValue->setText(tr("Missing"));
+}
+
+void ImagePalette::syncSelectionFromDocument()
+{
+ if (!m_doc || !m_doc->m_Selection || m_doc->m_Selection->count() != 1)
+ return;
+
+ PageItem* item = m_doc->m_Selection->itemAt(0);
+ QModelIndex index = m_model->indexForItem(item);
+ if (!index.isValid())
+ return;
+
+ const QSignalBlocker blocker(m_imageView->selectionModel());
+ m_imageView->setCurrentIndex(index);
+ showItem(item);
+}
+
+void ImagePalette::updateSummary()
+{
+ if (!m_doc)
+ {
+ m_summaryLabel->setText(tr("No document"));
+ return;
+ }
+
+ const int count = m_model->rowCount();
+ m_summaryLabel->setText(count == 1 ? tr("1 image frame") : tr("%1 image frames").arg(count));
+}
Index: scribus/ui/imagepalette.h
===================================================================
--- scribus/ui/imagepalette.h (nonexistent)
+++ scribus/ui/imagepalette.h (working copy)
@@ -0,0 +1,84 @@
+/*
+For general Scribus (>=1.3.2) copyright and licensing information please refer
+to the COPYING file provided with the program. Following this notice may exist
+a copyright and/or license notice that predates the release of Scribus 1.3.2
+for which a new license (GPL+exception) is in place.
+*/
+
+#ifndef IMAGEPALETTE_H
+#define IMAGEPALETTE_H
+
+#include "docks/dock_panelbase.h"
+#include "scguardedptr.h"
+#include "scribusapi.h"
+
+class QLabel;
+class QListView;
+class QTabBar;
+class QTimer;
+class QItemSelection;
+
+class ImagePaletteDocumentModel;
+class PageItem;
+class ScribusDoc;
+class ScribusMainWindow;
+
+class SCRIBUS_API ImagePalette : public DockPanelBase
+{
+ Q_OBJECT
+
+public:
+ explicit ImagePalette(QWidget* parent = nullptr);
+ ~ImagePalette() override = default;
+
+ void setMainWindow(ScribusMainWindow* mw);
+
+public slots:
+ void setDoc(ScribusDoc* doc);
+ void unsetDoc();
+
+protected:
+ void changeEvent(QEvent* event) override;
+
+private slots:
+ void scopeChanged(int index);
+ void handleDocumentChanged();
+ void handleDocumentSelectionChanged();
+ void handleCurrentPageChanged();
+ void handleVisibilityChanged(bool visible);
+ void handleViewSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
+
+private:
+ void clearInspector();
+ void languageChange();
+ void rebuild();
+ void scheduleRebuild();
+ void showItem(PageItem* item);
+ void syncSelectionFromDocument();
+ void updateSummary();
+
+ ImagePaletteDocumentModel* m_model {nullptr};
+ ScribusMainWindow* m_ScMW {nullptr};
+ ScGuardedPtr<ScribusDoc> m_doc {nullptr};
+ QTimer* m_refreshTimer {nullptr};
+ bool m_dirty {false};
+
+ QTabBar* m_scopeTabs {nullptr};
+ QListView* m_imageView {nullptr};
+ QLabel* m_nameValue {nullptr};
+ QLabel* m_pathLabel {nullptr};
+ QLabel* m_pathValue {nullptr};
+ QLabel* m_pageLabel {nullptr};
+ QLabel* m_pageValue {nullptr};
+ QLabel* m_frameLabel {nullptr};
+ QLabel* m_frameValue {nullptr};
+ QLabel* m_dimensionsLabel {nullptr};
+ QLabel* m_dimensionsValue {nullptr};
+ QLabel* m_resolutionLabel {nullptr};
+ QLabel* m_resolutionValue {nullptr};
+ QLabel* m_statusLabel {nullptr};
+ QLabel* m_statusValue {nullptr};
+ QLabel* m_summaryLabel {nullptr};
+};
+
+#endif // IMAGEPALETTE_H
Index: scribus/ui/imagepalette_documentmodel.cpp
===================================================================
--- scribus/ui/imagepalette_documentmodel.cpp (nonexistent)
+++ scribus/ui/imagepalette_documentmodel.cpp (working copy)
@@ -0,0 +1,170 @@
+/*
+For general Scribus (>=1.3.2) copyright and licensing information please refer
+to the COPYING file provided with the program. Following this notice may exist
+a copyright and/or license notice that predates the release of Scribus 1.3.2
+for which a new license (GPL+exception) is in place.
+*/
+
+#include "imagepalette_documentmodel.h"
+
+#include <QFileInfo>
+#include <QPainter>
+
+#include "pageitem.h"
+#include "pageitemiterator.h"
+#include "scpage.h"
+#include "scribusdoc.h"
+
+ImagePaletteDocumentModel::ImagePaletteDocumentModel(QObject* parent)
+ : QAbstractListModel(parent)
+{
+}
+
+int ImagePaletteDocumentModel::rowCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return m_entries.count();
+}
+
+QVariant ImagePaletteDocumentModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.count())
+ return QVariant();
+
+ const Entry& entry = m_entries.at(index.row());
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ return entry.displayText;
+ case Qt::DecorationRole:
+ return entry.thumbnail;
+ case Qt::ToolTipRole:
+ return entry.filePath.isEmpty()
+ ? entry.pageLabel
+ : QStringLiteral("%1\n%2").arg(entry.filePath, entry.pageLabel);
+ default:
+ break;
+ }
+ return QVariant();
+}
+
+void ImagePaletteDocumentModel::setDocument(ScribusDoc* doc)
+{
+ if (m_doc == doc)
+ return;
+
+ beginResetModel();
+ m_doc = doc;
+ m_entries.clear();
+ endResetModel();
+}
+
+void ImagePaletteDocumentModel::setCurrentPageOnly(bool currentPageOnly)
+{
+ m_currentPageOnly = currentPageOnly;
+}
+
+void ImagePaletteDocumentModel::rebuild()
+{
+ beginResetModel();
+ m_entries.clear();
+
+ if (m_doc)
+ {
+ const int options = PageItemIterator::IterateInGroups
+ | PageItemIterator::IterateInDocItems
+ | PageItemIterator::IterateInMasterItems;
+ PageItemIterator iterator(m_doc, options);
+ for (PageItem* item = iterator.current(); item; item = iterator.next())
+ {
+ if (!acceptsItem(item))
+ continue;
+
+ Entry entry;
+ entry.item = item;
+ entry.pageLabel = pageLabel(item);
+ entry.filePath = item->isInlineImage ? QString() : item->Pfile;
+ entry.thumbnail = createThumbnail(item);
+ entry.displayText = QStringLiteral("%1\n%2").arg(imageName(item), entry.pageLabel);
+ m_entries.append(entry);
+ }
+ }
+
+ endResetModel();
+}
+
+PageItem* ImagePaletteDocumentModel::itemAt(const QModelIndex& index) const
+{
+ if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.count())
+ return nullptr;
+ return m_entries.at(index.row()).item.data();
+}
+
+QModelIndex ImagePaletteDocumentModel::indexForItem(PageItem* item) const
+{
+ for (int row = 0; row < m_entries.count(); ++row)
+ {
+ if (m_entries.at(row).item.data() == item)
+ return index(row, 0);
+ }
+ return QModelIndex();
+}
+
+bool ImagePaletteDocumentModel::acceptsItem(PageItem* item) const
+{
+ if (!item || item->itemType() != PageItem::ImageFrame || item->isLatexFrame())
+ return false;
+ if (!m_currentPageOnly || !m_doc || !m_doc->currentPage())
+ return true;
+
+ const ScPage* currentPage = m_doc->currentPage();
+ if (m_doc->masterPageMode())
+ return !item->OnMasterPage.isEmpty() && item->OnMasterPage == currentPage->pageName();
+
+ if (item->OnMasterPage.isEmpty())
+ return item->OwnPage == currentPage->pageNr();
+
+ return item->OnMasterPage == currentPage->masterPageName();
+}
+
+QString ImagePaletteDocumentModel::imageName(PageItem* item) const
+{
+ if (item->isInlineImage)
+ return tr("Embedded Image");
+ if (item->Pfile.isEmpty())
+ return tr("Empty Image Frame");
+ return QFileInfo(item->Pfile).fileName();
+}
+
+QString ImagePaletteDocumentModel::pageLabel(PageItem* item) const
+{
+ if (!item->OnMasterPage.isEmpty())
+ return tr("Master: %1").arg(item->OnMasterPage);
+ if (item->OwnPage < 0)
+ return tr("Not on a Page");
+ return tr("Page %1").arg(item->OwnPage + 1);
+}
+
+QIcon ImagePaletteDocumentModel::createThumbnail(PageItem* item) const
+{
+ QPixmap pm(128, 128);
+ pm.fill(Qt::transparent);
+
+ QPainter painter(&pm);
+ const QRect imageRect(6, 6, 116, 116);
+ if (item->imageIsAvailable && item->pixm.width() > 0 && item->pixm.height() > 0)
+ {
+ const QImage preview = item->pixm.scaled(imageRect.width(), imageRect.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ const QPoint pos(imageRect.x() + (imageRect.width() - preview.width()) / 2,
+ imageRect.y() + (imageRect.height() - preview.height()) / 2);
+ painter.drawImage(pos, preview);
+ }
+ else
+ {
+ painter.setPen(QPen(Qt::red, 2));
+ painter.drawLine(imageRect.topLeft(), imageRect.bottomRight());
+ painter.drawLine(imageRect.bottomLeft(), imageRect.topRight());
+ }
+ return QIcon(pm);
+}
Index: scribus/ui/imagepalette_documentmodel.h
===================================================================
--- scribus/ui/imagepalette_documentmodel.h (nonexistent)
+++ scribus/ui/imagepalette_documentmodel.h (working copy)
@@ -0,0 +1,58 @@
+/*
+For general Scribus (>=1.3.2) copyright and licensing information please refer
+to the COPYING file provided with the program. Following this notice may exist
+a copyright and/or license notice that predates the release of Scribus 1.3.2
+for which a new license (GPL+exception) is in place.
+*/
+
+#ifndef IMAGEPALETTE_DOCUMENTMODEL_H
+#define IMAGEPALETTE_DOCUMENTMODEL_H
+
+#include <QAbstractListModel>
+#include <QIcon>
+#include <QPointer>
+#include <QVector>
+
+#include "scribusapi.h"
+
+class PageItem;
+class ScribusDoc;
+
+class SCRIBUS_API ImagePaletteDocumentModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ explicit ImagePaletteDocumentModel(QObject* parent = nullptr);
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+
+ void setDocument(ScribusDoc* doc);
+ void setCurrentPageOnly(bool currentPageOnly);
+ void rebuild();
+
+ PageItem* itemAt(const QModelIndex& index) const;
+ QModelIndex indexForItem(PageItem* item) const;
+
+private:
+ struct Entry
+ {
+ QPointer<PageItem> item;
+ QString displayText;
+ QString pageLabel;
+ QString filePath;
+ QIcon thumbnail;
+ };
+
+ bool acceptsItem(PageItem* item) const;
+ QString imageName(PageItem* item) const;
+ QString pageLabel(PageItem* item) const;
+ QIcon createThumbnail(PageItem* item) const;
+
+ ScribusDoc* m_doc {nullptr};
+ bool m_currentPageOnly {false};
+ QVector<Entry> m_entries;
+};
+
+#endif // IMAGEPALETTE_DOCUMENTMODEL_H
img2-images-document-management-v1.33.patch (50,790 bytes)
Index: scribus/ui/imagepalette_documentmodel.h
===================================================================
--- scribus/ui/imagepalette_documentmodel.h (revision 27814)
+++ scribus/ui/imagepalette_documentmodel.h (working copy)
@@ -23,17 +23,26 @@
Q_OBJECT
public:
+ enum class Scope
+ {
+ Document,
+ CurrentPage,
+ Missing
+ };
+
explicit ImagePaletteDocumentModel(QObject* parent = nullptr);
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
void setDocument(ScribusDoc* doc);
- void setCurrentPageOnly(bool currentPageOnly);
+ void setScope(Scope scope);
void rebuild();
PageItem* itemAt(const QModelIndex& index) const;
QModelIndex indexForItem(PageItem* item) const;
+ int missingCount() const;
+ QVector<PageItem*> placementsForItem(PageItem* item) const;
private:
struct Entry
@@ -46,12 +55,16 @@
};
bool acceptsItem(PageItem* item) const;
+ bool isDocumentImage(PageItem* item) const;
+ bool isMissing(PageItem* item) const;
QString imageName(PageItem* item) const;
QString pageLabel(PageItem* item) const;
+ QString comparablePath(PageItem* item) const;
QIcon createThumbnail(PageItem* item) const;
ScribusDoc* m_doc {nullptr};
- bool m_currentPageOnly {false};
+ Scope m_scope {Scope::Document};
+ int m_missingCount {0};
QVector<Entry> m_entries;
};
Index: scribus/ui/imagepalette_documentmodel.cpp
===================================================================
--- scribus/ui/imagepalette_documentmodel.cpp (revision 27814)
+++ scribus/ui/imagepalette_documentmodel.cpp (working copy)
@@ -7,6 +7,7 @@
#include "imagepalette_documentmodel.h"
+#include <QDir>
#include <QFileInfo>
#include <QPainter>
@@ -42,7 +43,7 @@
case Qt::ToolTipRole:
return entry.filePath.isEmpty()
? entry.pageLabel
- : QStringLiteral("%1\n%2").arg(entry.filePath, entry.pageLabel);
+ : QStringLiteral("%1\n%2").arg(QDir::toNativeSeparators(entry.filePath), entry.pageLabel);
default:
break;
}
@@ -56,19 +57,21 @@
beginResetModel();
m_doc = doc;
+ m_missingCount = 0;
m_entries.clear();
endResetModel();
}
-void ImagePaletteDocumentModel::setCurrentPageOnly(bool currentPageOnly)
+void ImagePaletteDocumentModel::setScope(Scope scope)
{
- m_currentPageOnly = currentPageOnly;
+ m_scope = scope;
}
void ImagePaletteDocumentModel::rebuild()
{
beginResetModel();
m_entries.clear();
+ m_missingCount = 0;
if (m_doc)
{
@@ -78,6 +81,8 @@
PageItemIterator iterator(m_doc, options);
for (PageItem* item = iterator.current(); item; item = iterator.next())
{
+ if (isDocumentImage(item) && isMissing(item))
+ ++m_missingCount;
if (!acceptsItem(item))
continue;
@@ -111,11 +116,43 @@
return QModelIndex();
}
+int ImagePaletteDocumentModel::missingCount() const
+{
+ return m_missingCount;
+}
+
+QVector<PageItem*> ImagePaletteDocumentModel::placementsForItem(PageItem* item) const
+{
+ QVector<PageItem*> placements;
+ if (!m_doc || !isDocumentImage(item))
+ return placements;
+ if (item->isInlineImage || item->Pfile.isEmpty())
+ {
+ placements.append(item);
+ return placements;
+ }
+
+ const QString path = comparablePath(item);
+ const int options = PageItemIterator::IterateInGroups
+ | PageItemIterator::IterateInDocItems
+ | PageItemIterator::IterateInMasterItems;
+ PageItemIterator iterator(m_doc, options);
+ for (PageItem* candidate = iterator.current(); candidate; candidate = iterator.next())
+ {
+ if (isDocumentImage(candidate) && !candidate->isInlineImage && comparablePath(candidate) == path)
+ placements.append(candidate);
+ }
+ return placements;
+}
+
bool ImagePaletteDocumentModel::acceptsItem(PageItem* item) const
{
- if (!item || item->itemType() != PageItem::ImageFrame || item->isLatexFrame())
+ if (!isDocumentImage(item))
return false;
- if (!m_currentPageOnly || !m_doc || !m_doc->currentPage())
+
+ if (m_scope == Scope::Missing)
+ return isMissing(item);
+ if (m_scope == Scope::Document || !m_doc || !m_doc->currentPage())
return true;
const ScPage* currentPage = m_doc->currentPage();
@@ -128,6 +165,16 @@
return item->OnMasterPage == currentPage->masterPageName();
}
+bool ImagePaletteDocumentModel::isDocumentImage(PageItem* item) const
+{
+ return item && item->itemType() == PageItem::ImageFrame && !item->isLatexFrame();
+}
+
+bool ImagePaletteDocumentModel::isMissing(PageItem* item) const
+{
+ return !item->isInlineImage && !item->Pfile.isEmpty() && !item->imageIsAvailable && !QFileInfo::exists(item->Pfile);
+}
+
QString ImagePaletteDocumentModel::imageName(PageItem* item) const
{
if (item->isInlineImage)
@@ -139,11 +186,21 @@
QString ImagePaletteDocumentModel::pageLabel(PageItem* item) const
{
+ QString page;
if (!item->OnMasterPage.isEmpty())
- return tr("Master: %1").arg(item->OnMasterPage);
- if (item->OwnPage < 0)
- return tr("Not on a Page");
- return tr("Page %1").arg(item->OwnPage + 1);
+ page = tr("Master: %1").arg(item->OnMasterPage);
+ else if (item->OwnPage < 0)
+ page = tr("Not on a Page");
+ else
+ page = tr("Page %1").arg(item->OwnPage + 1);
+ return tr("%1 · Frame: %2").arg(page, item->itemName());
+}
+
+QString ImagePaletteDocumentModel::comparablePath(PageItem* item) const
+{
+ if (!item || item->Pfile.isEmpty())
+ return QString();
+ return QDir::cleanPath(QFileInfo(item->Pfile).absoluteFilePath());
}
QIcon ImagePaletteDocumentModel::createThumbnail(PageItem* item) const
Index: scribus/ui/imagepalette.h
===================================================================
--- scribus/ui/imagepalette.h (revision 27814)
+++ scribus/ui/imagepalette.h (working copy)
@@ -8,14 +8,24 @@
#ifndef IMAGEPALETTE_H
#define IMAGEPALETTE_H
+#include <QList>
+#include <QString>
+
#include "docks/dock_panelbase.h"
#include "scguardedptr.h"
#include "scribusapi.h"
+class QCheckBox;
class QLabel;
class QListView;
+class QPushButton;
+class QGridLayout;
+class QResizeEvent;
+class QSplitter;
class QTabBar;
class QTimer;
+class QToolButton;
+class QWidget;
class QItemSelection;
class ImagePaletteDocumentModel;
@@ -39,6 +49,7 @@
protected:
void changeEvent(QEvent* event) override;
+ void resizeEvent(QResizeEvent* event) override;
private slots:
void scopeChanged(int index);
@@ -47,14 +58,38 @@
void handleCurrentPageChanged();
void handleVisibilityChanged(bool visible);
void handleViewSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
+ void previousPlacement();
+ void nextPlacement();
+ void selectFrame();
+ void relinkItem();
+ void searchForItem();
+ void openFileLocation();
+ void editImage();
+ void imageEffects();
+ void imageProperties();
+ void printToggled(bool checked);
+ void visibilityToggled(bool checked);
private:
void clearInspector();
+ PageItem* currentItem() const;
+ QString formatText(PageItem* item) const;
void languageChange();
+ void navigatePlacement(int offset);
+ void navigateToItem(PageItem* item);
+ bool loadPict(PageItem* item, const QString& filePath);
void rebuild();
+ void relinkMatchingImages(const QString& sourcePath, const QString& targetPath, bool brokenLinksOnly);
void scheduleRebuild();
+ bool selectPaletteItem(PageItem* item, bool ensureVisible = false);
void showItem(PageItem* item);
void syncSelectionFromDocument();
+ void updateActionState(PageItem* item);
+ void updateActionLayout(const QList<QPushButton*>& visibleButtons);
+ void updateInspectorLabelWidth();
+ void updateInspectorMinimumWidth();
+ void updateElidedLabels();
+ void updatePlacementState(PageItem* item);
void updateSummary();
ImagePaletteDocumentModel* m_model {nullptr};
@@ -62,22 +97,66 @@
ScGuardedPtr<ScribusDoc> m_doc {nullptr};
QTimer* m_refreshTimer {nullptr};
bool m_dirty {false};
+ bool m_syncSelectionAfterRebuild {false};
+ bool m_syncingSelection {false};
QTabBar* m_scopeTabs {nullptr};
QListView* m_imageView {nullptr};
+ QSplitter* m_splitter {nullptr};
+ QWidget* m_inspector {nullptr};
QLabel* m_nameValue {nullptr};
- QLabel* m_pathLabel {nullptr};
+ QLabel* m_statusLabel {nullptr};
+ QLabel* m_statusValue {nullptr};
QLabel* m_pathValue {nullptr};
+ QString m_nameText;
+ QString m_pathText;
+ QString m_pageText;
+ QString m_frameText;
+
+ QWidget* m_placementWidget {nullptr};
+ QGridLayout* m_infoLayout {nullptr};
+ QLabel* m_placementLabel {nullptr};
+ QLabel* m_placementPositionLabel {nullptr};
+ QToolButton* m_previousPlacementButton {nullptr};
+ QToolButton* m_nextPlacementButton {nullptr};
QLabel* m_pageLabel {nullptr};
QLabel* m_pageValue {nullptr};
QLabel* m_frameLabel {nullptr};
QLabel* m_frameValue {nullptr};
+
+ QLabel* m_detailsLabel {nullptr};
+ QWidget* m_detailsWidget {nullptr};
+ QGridLayout* m_detailsLayout {nullptr};
+ QLabel* m_formatLabel {nullptr};
+ QLabel* m_formatValue {nullptr};
+ QLabel* m_colorSpaceLabel {nullptr};
+ QLabel* m_colorSpaceValue {nullptr};
QLabel* m_dimensionsLabel {nullptr};
QLabel* m_dimensionsValue {nullptr};
+ QLabel* m_nativeResolutionLabel {nullptr};
+ QLabel* m_nativeResolutionValue {nullptr};
QLabel* m_resolutionLabel {nullptr};
QLabel* m_resolutionValue {nullptr};
- QLabel* m_statusLabel {nullptr};
- QLabel* m_statusValue {nullptr};
+ QLabel* m_scaleLabel {nullptr};
+ QLabel* m_scaleValue {nullptr};
+ QLabel* m_frameSizeLabel {nullptr};
+ QLabel* m_frameSizeValue {nullptr};
+ QLabel* m_scaledImageSizeLabel {nullptr};
+ QLabel* m_scaledImageSizeValue {nullptr};
+
+ QCheckBox* m_visibleCheck {nullptr};
+ QCheckBox* m_printCheck {nullptr};
+
+ QPushButton* m_searchButton {nullptr};
+ QWidget* m_actionsWidget {nullptr};
+ QGridLayout* m_actionLayout {nullptr};
+ QPushButton* m_selectButton {nullptr};
+ QPushButton* m_relinkButton {nullptr};
+ QPushButton* m_revealButton {nullptr};
+ QPushButton* m_editButton {nullptr};
+ QPushButton* m_effectsButton {nullptr};
+ QPushButton* m_propertiesButton {nullptr};
+
QLabel* m_summaryLabel {nullptr};
};
Index: scribus/ui/imagepalette.cpp
===================================================================
--- scribus/ui/imagepalette.cpp (revision 27814)
+++ scribus/ui/imagepalette.cpp (working copy)
@@ -8,25 +8,44 @@
#include "imagepalette.h"
#include <QAbstractItemView>
+#include <QCheckBox>
+#include <QDesktopServices>
#include <QDir>
#include <QEvent>
#include <QFileInfo>
-#include <QFormLayout>
-#include <QFrame>
+#include <QGridLayout>
+#include <QHBoxLayout>
#include <QItemSelectionModel>
#include <QLabel>
#include <QListView>
+#include <QMargins>
+#include <QPushButton>
+#include <QResizeEvent>
+#include <QScopedPointer>
#include <QSignalBlocker>
+#include <QSizePolicy>
#include <QSplitter>
+#include <QStyle>
#include <QTabBar>
#include <QTimer>
+#include <QToolButton>
+#include <QUrl>
#include <QVBoxLayout>
#include "imagepalette_documentmodel.h"
#include "pageitem.h"
+#include "pageitemiterator.h"
#include "selection.h"
#include "scribus.h"
#include "scribusdoc.h"
+#include "units.h"
+#include "util_color.h"
+#include "util_formats.h"
+#include "ui/contentpalette.h"
+#include "ui/customfdialog.h"
+#include "ui/picsearch.h"
+#include "ui/picsearchoptions.h"
+#include "ui/scmessagebox.h"
ImagePalette::ImagePalette(QWidget* parent)
: DockPanelBase("Images", "image-collection", parent),
@@ -45,82 +64,184 @@
m_scopeTabs->setExpanding(false);
m_scopeTabs->addTab(QString());
m_scopeTabs->addTab(QString());
+ m_scopeTabs->addTab(QString());
layout->addWidget(m_scopeTabs);
- auto* splitter = new QSplitter(Qt::Horizontal, panel);
- splitter->setChildrenCollapsible(false);
- layout->addWidget(splitter, 1);
+ m_splitter = new QSplitter(Qt::Horizontal, panel);
+ m_splitter->setChildrenCollapsible(false);
+ layout->addWidget(m_splitter, 1);
- m_imageView = new QListView(splitter);
- m_imageView->setMinimumSize(220, 180);
+ m_imageView = new QListView(m_splitter);
+ m_imageView->setMinimumSize(180, 180);
m_imageView->setModel(m_model);
m_imageView->setIconSize(QSize(128, 128));
- m_imageView->setGridSize(QSize(156, 174));
+ m_imageView->setGridSize(QSize(156, 166));
m_imageView->setResizeMode(QListView::Adjust);
m_imageView->setViewMode(QListView::IconMode);
m_imageView->setMovement(QListView::Static);
m_imageView->setFlow(QListView::LeftToRight);
m_imageView->setWrapping(true);
- m_imageView->setWordWrap(true);
+ m_imageView->setWordWrap(false);
+ m_imageView->setTextElideMode(Qt::ElideMiddle);
m_imageView->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_imageView->setSelectionMode(QAbstractItemView::SingleSelection);
- auto* inspector = new QWidget(splitter);
- inspector->setMinimumWidth(210);
+ m_inspector = new QWidget(m_splitter);
+ auto* inspector = m_inspector;
auto* inspectorLayout = new QVBoxLayout(inspector);
- inspectorLayout->setContentsMargins(8, 0, 0, 0);
+ inspectorLayout->setContentsMargins(8, 0, 8, 0);
m_nameValue = new QLabel(inspector);
QFont nameFont = m_nameValue->font();
nameFont.setBold(true);
m_nameValue->setFont(nameFont);
- m_nameValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_nameValue->setWordWrap(false);
+ m_nameValue->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
inspectorLayout->addWidget(m_nameValue);
- auto* line = new QFrame(inspector);
- line->setFrameShape(QFrame::HLine);
- line->setFrameShadow(QFrame::Sunken);
- inspectorLayout->addWidget(line);
-
- auto* form = new QFormLayout();
- form->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
- form->setLabelAlignment(Qt::AlignLeft | Qt::AlignTop);
- inspectorLayout->addLayout(form);
-
- m_pathLabel = new QLabel(inspector);
m_pathValue = new QLabel(inspector);
- m_pathValue->setWordWrap(true);
- m_pathValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
- form->addRow(m_pathLabel, m_pathValue);
-
- m_pageLabel = new QLabel(inspector);
- m_pageValue = new QLabel(inspector);
- m_pageValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
- form->addRow(m_pageLabel, m_pageValue);
-
- m_frameLabel = new QLabel(inspector);
- m_frameValue = new QLabel(inspector);
- m_frameValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
- form->addRow(m_frameLabel, m_frameValue);
+ m_pathValue->setWordWrap(false);
+ m_pathValue->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
+ inspectorLayout->addWidget(m_pathValue);
+ inspectorLayout->addStretch(1);
+
+ m_placementWidget = new QWidget(inspector);
+ m_infoLayout = new QGridLayout(m_placementWidget);
+ m_infoLayout->setContentsMargins(0, 0, 0, 0);
+ m_infoLayout->setHorizontalSpacing(12);
+ m_infoLayout->setColumnStretch(0, 0);
+ m_infoLayout->setColumnStretch(1, 1);
+
+ m_statusLabel = new QLabel(m_placementWidget);
+ m_statusValue = new QLabel(m_placementWidget);
+ m_infoLayout->addWidget(m_statusLabel, 0, 0);
+ m_infoLayout->addWidget(m_statusValue, 0, 1);
+
+ m_placementLabel = new QLabel(m_placementWidget);
+ auto* placementControls = new QWidget(m_placementWidget);
+ auto* placementLayout = new QHBoxLayout(placementControls);
+ placementLayout->setContentsMargins(0, 0, 0, 0);
+ m_previousPlacementButton = new QToolButton(placementControls);
+ m_previousPlacementButton->setArrowType(Qt::LeftArrow);
+ m_previousPlacementButton->setAutoRaise(true);
+ m_placementPositionLabel = new QLabel(placementControls);
+ m_nextPlacementButton = new QToolButton(placementControls);
+ m_nextPlacementButton->setArrowType(Qt::RightArrow);
+ m_nextPlacementButton->setAutoRaise(true);
+ placementLayout->addWidget(m_placementPositionLabel);
+ placementLayout->addWidget(m_previousPlacementButton);
+ placementLayout->addWidget(m_nextPlacementButton);
+ placementLayout->addStretch(1);
+ m_infoLayout->addWidget(m_placementLabel, 1, 0);
+ m_infoLayout->addWidget(placementControls, 1, 1);
+
+ m_pageLabel = new QLabel(m_placementWidget);
+ m_pageValue = new QLabel(m_placementWidget);
+ m_pageValue->setWordWrap(false);
+ m_pageValue->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
+ m_infoLayout->addWidget(m_pageLabel, 2, 0);
+ m_infoLayout->addWidget(m_pageValue, 2, 1);
+
+ m_frameLabel = new QLabel(m_placementWidget);
+ m_frameValue = new QLabel(m_placementWidget);
+ m_frameValue->setWordWrap(false);
+ m_frameValue->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
+ m_infoLayout->addWidget(m_frameLabel, 3, 0);
+ m_infoLayout->addWidget(m_frameValue, 3, 1);
+
+ auto* visibilityControls = new QWidget(m_placementWidget);
+ auto* visibilityLayout = new QVBoxLayout(visibilityControls);
+ const int sectionSpacing = qMax(0, style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing, nullptr, m_placementWidget));
+ visibilityLayout->setContentsMargins(0, sectionSpacing, 0, 0);
+ m_visibleCheck = new QCheckBox(visibilityControls);
+ m_printCheck = new QCheckBox(visibilityControls);
+ visibilityLayout->addWidget(m_visibleCheck);
+ visibilityLayout->addWidget(m_printCheck);
+ m_infoLayout->addWidget(visibilityControls, 4, 1, Qt::AlignLeft);
+ inspectorLayout->addWidget(m_placementWidget);
+ inspectorLayout->addStretch(1);
+
+ m_actionsWidget = new QWidget(inspector);
+ m_actionLayout = new QGridLayout(m_actionsWidget);
+ m_actionLayout->setContentsMargins(0, 0, 0, 0);
+ m_actionLayout->setHorizontalSpacing(12);
+ m_actionLayout->setColumnStretch(0, 0);
+ m_actionLayout->setColumnStretch(1, 1);
+ m_actionLayout->setAlignment(Qt::AlignLeft | Qt::AlignTop);
+ m_searchButton = new QPushButton(m_actionsWidget);
+ m_selectButton = new QPushButton(m_actionsWidget);
+ m_relinkButton = new QPushButton(m_actionsWidget);
+ m_revealButton = new QPushButton(m_actionsWidget);
+ m_editButton = new QPushButton(m_actionsWidget);
+ m_effectsButton = new QPushButton(m_actionsWidget);
+ m_propertiesButton = new QPushButton(m_actionsWidget);
+ inspectorLayout->addWidget(m_actionsWidget);
+ inspectorLayout->addStretch(1);
- m_dimensionsLabel = new QLabel(inspector);
- m_dimensionsValue = new QLabel(inspector);
+ m_detailsLabel = new QLabel(inspector);
+ QFont detailsFont = m_detailsLabel->font();
+ detailsFont.setBold(true);
+ m_detailsLabel->setFont(detailsFont);
+ inspectorLayout->addWidget(m_detailsLabel);
+
+ m_detailsWidget = new QWidget(inspector);
+ m_detailsLayout = new QGridLayout(m_detailsWidget);
+ m_detailsLayout->setContentsMargins(0, 0, 0, 0);
+ m_detailsLayout->setHorizontalSpacing(12);
+ m_detailsLayout->setColumnStretch(0, 0);
+ m_detailsLayout->setColumnStretch(1, 1);
+
+ m_formatLabel = new QLabel(m_detailsWidget);
+ m_formatValue = new QLabel(m_detailsWidget);
+ m_formatValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_detailsLayout->addWidget(m_formatLabel, 0, 0);
+ m_detailsLayout->addWidget(m_formatValue, 0, 1);
+
+ m_colorSpaceLabel = new QLabel(m_detailsWidget);
+ m_colorSpaceValue = new QLabel(m_detailsWidget);
+ m_colorSpaceValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_detailsLayout->addWidget(m_colorSpaceLabel, 1, 0);
+ m_detailsLayout->addWidget(m_colorSpaceValue, 1, 1);
+
+ m_dimensionsLabel = new QLabel(m_detailsWidget);
+ m_dimensionsValue = new QLabel(m_detailsWidget);
m_dimensionsValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
- form->addRow(m_dimensionsLabel, m_dimensionsValue);
+ m_detailsLayout->addWidget(m_dimensionsLabel, 2, 0);
+ m_detailsLayout->addWidget(m_dimensionsValue, 2, 1);
- m_resolutionLabel = new QLabel(inspector);
- m_resolutionValue = new QLabel(inspector);
- m_resolutionValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
- form->addRow(m_resolutionLabel, m_resolutionValue);
+ m_nativeResolutionLabel = new QLabel(m_detailsWidget);
+ m_nativeResolutionValue = new QLabel(m_detailsWidget);
+ m_nativeResolutionValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_detailsLayout->addWidget(m_nativeResolutionLabel, 3, 0);
+ m_detailsLayout->addWidget(m_nativeResolutionValue, 3, 1);
- m_statusLabel = new QLabel(inspector);
- m_statusValue = new QLabel(inspector);
- m_statusValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
- form->addRow(m_statusLabel, m_statusValue);
+ m_resolutionLabel = new QLabel(m_detailsWidget);
+ m_resolutionValue = new QLabel(m_detailsWidget);
+ m_resolutionValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_detailsLayout->addWidget(m_resolutionLabel, 4, 0);
+ m_detailsLayout->addWidget(m_resolutionValue, 4, 1);
- inspectorLayout->addStretch(1);
- splitter->setStretchFactor(0, 3);
- splitter->setStretchFactor(1, 2);
+ m_scaleLabel = new QLabel(m_detailsWidget);
+ m_scaleValue = new QLabel(m_detailsWidget);
+ m_scaleValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_detailsLayout->addWidget(m_scaleLabel, 5, 0);
+ m_detailsLayout->addWidget(m_scaleValue, 5, 1);
+
+ m_frameSizeLabel = new QLabel(m_detailsWidget);
+ m_frameSizeValue = new QLabel(m_detailsWidget);
+ m_frameSizeValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_detailsLayout->addWidget(m_frameSizeLabel, 6, 0);
+ m_detailsLayout->addWidget(m_frameSizeValue, 6, 1);
+
+ m_scaledImageSizeLabel = new QLabel(m_detailsWidget);
+ m_scaledImageSizeValue = new QLabel(m_detailsWidget);
+ m_scaledImageSizeValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_detailsLayout->addWidget(m_scaledImageSizeLabel, 7, 0);
+ m_detailsLayout->addWidget(m_scaledImageSizeValue, 7, 1);
+ inspectorLayout->addWidget(m_detailsWidget);
+ inspectorLayout->addStretch(12);
+ m_splitter->setStretchFactor(0, 3);
+ m_splitter->setStretchFactor(1, 2);
m_summaryLabel = new QLabel(panel);
layout->addWidget(m_summaryLabel);
@@ -133,8 +254,20 @@
connect(m_scopeTabs, &QTabBar::currentChanged, this, &ImagePalette::scopeChanged);
connect(m_imageView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &ImagePalette::handleViewSelectionChanged);
+ connect(m_splitter, &QSplitter::splitterMoved, this, [this](int, int) { updateElidedLabels(); });
connect(m_refreshTimer, &QTimer::timeout, this, &ImagePalette::rebuild);
connect(this, &DockPanelBase::visibilityChanged, this, &ImagePalette::handleVisibilityChanged);
+ connect(m_previousPlacementButton, &QToolButton::clicked, this, &ImagePalette::previousPlacement);
+ connect(m_nextPlacementButton, &QToolButton::clicked, this, &ImagePalette::nextPlacement);
+ connect(m_selectButton, &QPushButton::clicked, this, &ImagePalette::selectFrame);
+ connect(m_relinkButton, &QPushButton::clicked, this, &ImagePalette::relinkItem);
+ connect(m_searchButton, &QPushButton::clicked, this, &ImagePalette::searchForItem);
+ connect(m_revealButton, &QPushButton::clicked, this, &ImagePalette::openFileLocation);
+ connect(m_editButton, &QPushButton::clicked, this, &ImagePalette::editImage);
+ connect(m_effectsButton, &QPushButton::clicked, this, &ImagePalette::imageEffects);
+ connect(m_propertiesButton, &QPushButton::clicked, this, &ImagePalette::imageProperties);
+ connect(m_visibleCheck, &QCheckBox::toggled, this, &ImagePalette::visibilityToggled);
+ connect(m_printCheck, &QCheckBox::toggled, this, &ImagePalette::printToggled);
languageChange();
clearInspector();
@@ -172,6 +305,7 @@
m_doc = doc;
m_model->setDocument(m_doc);
+ m_syncSelectionAfterRebuild = true;
if (m_doc)
{
@@ -195,6 +329,7 @@
}
m_refreshTimer->stop();
+ m_syncSelectionAfterRebuild = false;
m_doc = nullptr;
m_model->setDocument(nullptr);
m_dirty = false;
@@ -209,9 +344,37 @@
languageChange();
}
+void ImagePalette::resizeEvent(QResizeEvent* event)
+{
+ DockPanelBase::resizeEvent(event);
+ updateElidedLabels();
+}
+
void ImagePalette::scopeChanged(int index)
{
- m_model->setCurrentPageOnly(index == 1);
+ ImagePaletteDocumentModel::Scope scope = ImagePaletteDocumentModel::Scope::Document;
+ if (index == 1)
+ scope = ImagePaletteDocumentModel::Scope::CurrentPage;
+ else if (index == 2)
+ scope = ImagePaletteDocumentModel::Scope::Missing;
+ m_model->setScope(scope);
+
+ if (scope == ImagePaletteDocumentModel::Scope::Missing)
+ {
+ m_imageView->setViewMode(QListView::ListMode);
+ m_imageView->setIconSize(QSize(48, 48));
+ m_imageView->setGridSize(QSize());
+ m_imageView->setFlow(QListView::TopToBottom);
+ m_imageView->setWrapping(false);
+ }
+ else
+ {
+ m_imageView->setViewMode(QListView::IconMode);
+ m_imageView->setIconSize(QSize(128, 128));
+ m_imageView->setGridSize(QSize(156, 166));
+ m_imageView->setFlow(QListView::LeftToRight);
+ m_imageView->setWrapping(true);
+ }
rebuild();
}
@@ -225,6 +388,7 @@
{
if (m_scopeTabs->currentIndex() == 1)
{
+ m_syncSelectionAfterRebuild = true;
m_dirty = true;
scheduleRebuild();
return;
@@ -239,6 +403,7 @@
if (m_scopeTabs->currentIndex() != 1)
return;
+ m_syncSelectionAfterRebuild = true;
m_dirty = true;
scheduleRebuild();
}
@@ -254,19 +419,230 @@
void ImagePalette::handleViewSelectionChanged(const QItemSelection&, const QItemSelection&)
{
- showItem(m_model->itemAt(m_imageView->currentIndex()));
+ if (m_syncingSelection)
+ return;
+
+ showItem(currentItem());
+ updateSummary();
+}
+
+void ImagePalette::previousPlacement()
+{
+ navigatePlacement(-1);
+}
+
+void ImagePalette::nextPlacement()
+{
+ navigatePlacement(1);
+}
+
+void ImagePalette::selectFrame()
+{
+ PageItem* item = currentItem();
+ if (!item || !m_ScMW || !m_doc)
+ return;
+
+ if (item->OnMasterPage.isEmpty() && m_doc->masterPageMode())
+ m_ScMW->closeActiveWindowMasterPageEditor();
+ else if (!item->OnMasterPage.isEmpty() && !m_doc->masterPageMode())
+ m_ScMW->editMasterPagesStart(item->OnMasterPage);
+
+ m_ScMW->selectItemsFromOutlines(item, true, 1);
+}
+
+void ImagePalette::relinkItem()
+{
+ PageItem* item = currentItem();
+ if (!item || !m_doc || item->isInlineImage)
+ return;
+
+ QString startDir = QFileInfo(item->Pfile).absolutePath();
+ if (startDir.isEmpty() || !QDir(startDir).exists())
+ startDir = QDir::homePath();
+
+ const QString format = FormatsManager::instance()->fileDialogFormatList(FormatsManager::IMAGESIMGFRAME);
+ CustomFDialog dialog(this, startDir, tr("Replace Image"), format,
+ fdShowPreview | fdExistingFiles | fdDisableOk, contextImages);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ const QString filePath = dialog.selectedFile();
+ if (filePath.isEmpty() || !loadPict(item, filePath))
+ return;
+
+ m_doc->changed();
+ rebuild();
+ selectPaletteItem(item, true);
+}
+
+void ImagePalette::searchForItem()
+{
+ PageItem* item = currentItem();
+ if (!item || !m_doc || item->isInlineImage || item->Pfile.isEmpty())
+ return;
+
+ static QString lastSearchPath;
+ const QFileInfo currentFile(item->Pfile);
+ if (lastSearchPath.isEmpty())
+ lastSearchPath = currentFile.absolutePath();
+
+ QScopedPointer<PicSearchOptions> searchOptions(new PicSearchOptions(this, currentFile.fileName(), lastSearchPath));
+ if (searchOptions->exec() != QDialog::Accepted)
+ return;
+
+ lastSearchPath = searchOptions->getLastDirSearched();
+ if (searchOptions->getMatches().isEmpty())
+ {
+ ScMessageBox::information(this, tr("Scribus - Find Missing Image"),
+ tr("No matching image files were found for \"%1\".").arg(searchOptions->getFileName()),
+ QMessageBox::Ok | QMessageBox::Default | QMessageBox::Escape,
+ QMessageBox::NoButton);
+ return;
+ }
+
+ const bool brokenLink = !item->imageIsAvailable;
+ QScopedPointer<PicSearch> searchResults(new PicSearch(this, searchOptions->getFileName(), searchOptions->getMatches(), brokenLink));
+ if (searchResults->exec() != QDialog::Accepted)
+ return;
+
+ const QString sourcePath = item->Pfile;
+ const QString targetPath = searchResults->getSelectedImage();
+ if (!loadPict(item, targetPath))
+ return;
+
+ if (searchResults->isApplyToMatchingImages())
+ relinkMatchingImages(sourcePath, targetPath, brokenLink);
+
+ m_doc->changed();
+ rebuild();
+ selectPaletteItem(item, true);
+}
+
+void ImagePalette::openFileLocation()
+{
+ PageItem* item = currentItem();
+ if (!item || item->isInlineImage || item->Pfile.isEmpty())
+ return;
+
+ const QString path = QFileInfo(item->Pfile).absolutePath();
+ if (QDir(path).exists())
+ QDesktopServices::openUrl(QUrl::fromLocalFile(path));
+}
+
+void ImagePalette::editImage()
+{
+ PageItem* item = currentItem();
+ if (!item || !m_ScMW || !item->imageIsAvailable || !item->isRaster)
+ return;
+
+ selectFrame();
+ m_ScMW->callImageEditor();
+}
+
+void ImagePalette::imageEffects()
+{
+ PageItem* item = currentItem();
+ if (!item || !m_ScMW || !item->imageIsAvailable || !item->isRaster)
+ return;
+
+ selectFrame();
+ m_ScMW->ImageEffects();
+}
+
+void ImagePalette::imageProperties()
+{
+ PageItem* item = currentItem();
+ if (!item || !m_ScMW || !m_ScMW->contentPalette)
+ return;
+
+ selectFrame();
+ m_ScMW->contentPalette->show();
+ m_ScMW->contentPalette->raise();
+}
+
+void ImagePalette::printToggled(bool checked)
+{
+ PageItem* item = currentItem();
+ if (!item || !m_doc || item->printEnabled() == checked)
+ return;
+
+ item->setPrintEnabled(checked);
+ item->update();
+ m_doc->changed();
+}
+
+void ImagePalette::visibilityToggled(bool checked)
+{
+ PageItem* item = currentItem();
+ if (!item || !m_doc || item->imageVisible() == checked)
+ return;
+
+ item->setImageVisible(checked);
+ item->update();
+ m_doc->changed();
}
void ImagePalette::clearInspector()
{
const QString empty = QString::fromUtf8("—");
- m_nameValue->setText(empty);
- m_pathValue->setText(empty);
- m_pageValue->setText(empty);
- m_frameValue->setText(empty);
+ m_nameText = empty;
+ m_pathText = empty;
+ m_pageText = empty;
+ m_frameText = empty;
+ updateElidedLabels();
+ m_formatValue->setText(empty);
+ m_colorSpaceValue->setText(empty);
m_dimensionsValue->setText(empty);
+ m_nativeResolutionValue->setText(empty);
m_resolutionValue->setText(empty);
+ m_scaleValue->setText(empty);
+ m_frameSizeValue->setText(empty);
+ m_scaledImageSizeValue->setText(empty);
+ m_placementPositionLabel->setText(empty);
+ m_previousPlacementButton->hide();
+ m_nextPlacementButton->hide();
m_statusValue->setText(empty);
+ {
+ const QSignalBlocker visibleBlocker(m_visibleCheck);
+ const QSignalBlocker printBlocker(m_printCheck);
+ m_visibleCheck->setChecked(false);
+ m_printCheck->setChecked(false);
+ }
+ updateActionState(nullptr);
+}
+
+PageItem* ImagePalette::currentItem() const
+{
+ return m_model->itemAt(m_imageView->currentIndex());
+}
+
+QString ImagePalette::formatText(PageItem* item) const
+{
+ if (!item || !item->imageIsAvailable)
+ return QString::fromUtf8("—");
+
+ const QString ext = QFileInfo(item->Pfile).suffix().toLower();
+ switch (item->pixm.imgInfo.type)
+ {
+ case 0:
+ return tr("JPEG");
+ case 1:
+ return tr("TIFF");
+ case 2:
+ return tr("PSD");
+ case 3:
+ return tr("EPS/PS");
+ case 4:
+ return tr("PDF");
+ case 5:
+ return tr("JPEG 2000");
+ case 6:
+ return ext.toUpper();
+ case 7:
+ return tr("Embedded PSD");
+ default:
+ return ext.isEmpty() ? tr("Unknown") : ext.toUpper();
+ }
}
void ImagePalette::languageChange()
@@ -274,12 +650,66 @@
setWindowTitle(tr("Images"));
m_scopeTabs->setTabText(0, tr("Document"));
m_scopeTabs->setTabText(1, tr("Current Page"));
- m_pathLabel->setText(tr("Path:"));
+ m_scopeTabs->setTabText(2, tr("Missing (%1)").arg(m_model->missingCount()));
+ m_statusLabel->setText(tr("Status:"));
+ m_placementLabel->setText(tr("Placement:"));
m_pageLabel->setText(tr("Page:"));
m_frameLabel->setText(tr("Frame:"));
- m_dimensionsLabel->setText(tr("Dimensions:"));
- m_resolutionLabel->setText(tr("Effective:"));
- m_statusLabel->setText(tr("Status:"));
+ m_detailsLabel->setText(tr("Details"));
+ m_formatLabel->setText(tr("Format:"));
+ m_colorSpaceLabel->setText(tr("Color Space:"));
+ m_dimensionsLabel->setText(tr("Pixel Size:"));
+ m_nativeResolutionLabel->setText(tr("Native DPI:"));
+ m_resolutionLabel->setText(tr("Effective DPI:"));
+ m_scaleLabel->setText(tr("Scale:"));
+ m_frameSizeLabel->setText(tr("Frame Size:"));
+ m_scaledImageSizeLabel->setText(tr("Scaled Image Size:"));
+ m_visibleCheck->setText(tr("Show"));
+ m_printCheck->setText(tr("Export/Print"));
+ m_selectButton->setText(tr("Select"));
+ m_relinkButton->setText(tr("Replace…"));
+ m_searchButton->setText(tr("Find…"));
+ m_revealButton->setText(tr("Location"));
+ m_editButton->setText(tr("Edit…"));
+ m_effectsButton->setText(tr("Effects…"));
+ m_propertiesButton->setText(tr("Properties"));
+
+ const int leftButtonWidth = qMax(qMax(m_selectButton->sizeHint().width(), m_editButton->sizeHint().width()),
+ qMax(m_effectsButton->sizeHint().width(), m_searchButton->sizeHint().width()));
+ const int rightButtonWidth = qMax(m_relinkButton->sizeHint().width(),
+ qMax(m_revealButton->sizeHint().width(), m_propertiesButton->sizeHint().width()));
+ m_selectButton->setFixedWidth(leftButtonWidth);
+ m_editButton->setFixedWidth(leftButtonWidth);
+ m_effectsButton->setFixedWidth(leftButtonWidth);
+ m_searchButton->setFixedWidth(leftButtonWidth);
+ m_relinkButton->setFixedWidth(rightButtonWidth);
+ m_revealButton->setFixedWidth(rightButtonWidth);
+ m_propertiesButton->setFixedWidth(rightButtonWidth);
+
+ m_previousPlacementButton->setToolTip(tr("Go to the previous placement of this image."));
+ m_nextPlacementButton->setToolTip(tr("Go to the next placement of this image."));
+ m_selectButton->setToolTip(tr("Select this image frame in the document."));
+ m_relinkButton->setToolTip(tr("Choose another image file for this frame."));
+ m_searchButton->setToolTip(tr("Search for the missing image file and relink it."));
+ m_revealButton->setToolTip(tr("Open the folder containing this image."));
+ m_editButton->setToolTip(tr("Open the image in the configured external image editor."));
+ m_effectsButton->setToolTip(tr("Apply image effects to this frame."));
+ m_propertiesButton->setToolTip(tr("Show this frame's image controls in Content Properties."));
+ m_visibleCheck->setToolTip(tr("Show or hide the image inside its frame."));
+ m_printCheck->setToolTip(tr("Include this image frame when exporting or printing."));
+
+ m_nativeResolutionLabel->setToolTip(tr("Original resolution stored for the image."));
+ m_nativeResolutionValue->setToolTip(m_nativeResolutionLabel->toolTip());
+ m_resolutionLabel->setToolTip(tr("Resolution after scaling the image in its frame."));
+ m_resolutionValue->setToolTip(m_resolutionLabel->toolTip());
+ m_scaleLabel->setToolTip(tr("Horizontal and vertical image scaling."));
+ m_scaleValue->setToolTip(m_scaleLabel->toolTip());
+ m_frameSizeLabel->setToolTip(tr("Width and height of the image frame."));
+ m_frameSizeValue->setToolTip(m_frameSizeLabel->toolTip());
+ m_scaledImageSizeLabel->setToolTip(tr("Full image dimensions at the current scale, before frame clipping."));
+ m_scaledImageSizeValue->setToolTip(m_scaledImageSizeLabel->toolTip());
+ updateInspectorLabelWidth();
+ updateInspectorMinimumWidth();
m_dirty = true;
if (isVisible())
rebuild();
@@ -287,18 +717,108 @@
updateSummary();
}
+void ImagePalette::navigatePlacement(int offset)
+{
+ PageItem* item = currentItem();
+ if (!item || !m_doc || !m_ScMW)
+ return;
+
+ const QVector<PageItem*> placements = m_model->placementsForItem(item);
+ const int current = placements.indexOf(item);
+ const int targetIndex = current + offset;
+ if (current < 0 || targetIndex < 0 || targetIndex >= placements.count())
+ return;
+
+ PageItem* target = placements.at(targetIndex);
+ navigateToItem(target);
+
+ if (m_scopeTabs->currentIndex() == 1)
+ rebuild();
+
+ selectPaletteItem(target, true);
+ updateSummary();
+}
+
+void ImagePalette::navigateToItem(PageItem* item)
+{
+ if (!item || !m_ScMW || !m_doc)
+ return;
+
+ if (item->OnMasterPage.isEmpty())
+ {
+ if (m_doc->masterPageMode())
+ m_ScMW->closeActiveWindowMasterPageEditor();
+ if (item->OwnPage >= 0)
+ m_ScMW->selectPagesFromOutlines(item->OwnPage);
+ }
+ else
+ m_ScMW->editMasterPagesStart(item->OnMasterPage);
+
+ m_ScMW->selectItemsFromOutlines(item, true, 1);
+}
+
+bool ImagePalette::loadPict(PageItem* item, const QString& filePath)
+{
+ if (!item || !m_doc || filePath.isEmpty())
+ return false;
+
+ item->Pfile = filePath;
+ const bool masterPageMode = !item->OnMasterPage.isEmpty();
+ const bool oldMasterPageMode = m_doc->masterPageMode();
+ if (masterPageMode != oldMasterPageMode)
+ m_doc->setMasterPageMode(masterPageMode);
+ m_doc->loadPict(filePath, item, true, true);
+ if (masterPageMode != oldMasterPageMode)
+ m_doc->setMasterPageMode(oldMasterPageMode);
+ return item->imageIsAvailable;
+}
+
void ImagePalette::rebuild()
{
m_refreshTimer->stop();
+ PageItem* paletteItem = currentItem();
+ const bool syncFromDocument = m_syncSelectionAfterRebuild;
+ m_syncSelectionAfterRebuild = false;
+
m_model->rebuild();
m_dirty = false;
- syncSelectionFromDocument();
+ m_scopeTabs->setTabText(2, tr("Missing (%1)").arg(m_model->missingCount()));
+
+ const bool restored = !syncFromDocument && paletteItem && selectPaletteItem(paletteItem);
+ if (!restored)
+ syncSelectionFromDocument();
updateSummary();
if (!m_imageView->currentIndex().isValid())
clearInspector();
}
+void ImagePalette::relinkMatchingImages(const QString& sourcePath, const QString& targetPath, bool brokenLinksOnly)
+{
+ if (!m_doc)
+ return;
+
+ const QFileInfo source(sourcePath);
+ const QFileInfo target(targetPath);
+ const int options = PageItemIterator::IterateInGroups
+ | PageItemIterator::IterateInDocItems
+ | PageItemIterator::IterateInMasterItems;
+ PageItemIterator iterator(m_doc, options);
+ for (PageItem* item = iterator.current(); item; item = iterator.next())
+ {
+ if (!item || item->itemType() != PageItem::ImageFrame || item->isLatexFrame() || item->isInlineImage)
+ continue;
+ if (brokenLinksOnly && item->imageIsAvailable)
+ continue;
+
+ const QFileInfo fileInfo(item->Pfile);
+ if (fileInfo.path() != source.path())
+ continue;
+
+ loadPict(item, QDir(target.path()).filePath(fileInfo.fileName()));
+ }
+}
+
void ImagePalette::scheduleRebuild()
{
if (!isVisible())
@@ -306,6 +826,29 @@
m_refreshTimer->start();
}
+bool ImagePalette::selectPaletteItem(PageItem* item, bool ensureVisible)
+{
+ if (!item || !m_imageView->selectionModel())
+ return false;
+
+ const QModelIndex index = m_model->indexForItem(item);
+ if (!index.isValid())
+ return false;
+
+ QItemSelectionModel* selectionModel = m_imageView->selectionModel();
+ m_syncingSelection = true;
+ selectionModel->clearSelection();
+ selectionModel->select(index, QItemSelectionModel::Select);
+ selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
+ m_syncingSelection = false;
+
+ if (ensureVisible)
+ m_imageView->scrollTo(index, QAbstractItemView::EnsureVisible);
+ showItem(currentItem());
+ updateSummary();
+ return true;
+}
+
void ImagePalette::showItem(PageItem* item)
{
if (!item)
@@ -314,58 +857,102 @@
return;
}
- QFileInfo fileInfo(item->Pfile);
+ const QString empty = QString::fromUtf8("—");
+ const QFileInfo fileInfo(item->Pfile);
if (item->isInlineImage)
{
- m_nameValue->setText(tr("Embedded Image"));
- m_pathValue->setText(tr("Embedded in document"));
+ m_nameText = tr("Embedded Image");
+ m_pathText = tr("Embedded in document");
}
else if (item->Pfile.isEmpty())
{
- m_nameValue->setText(tr("Empty Image Frame"));
- m_pathValue->setText(QString::fromUtf8("—"));
+ m_nameText = tr("Empty Image Frame");
+ m_pathText = empty;
}
else
{
- m_nameValue->setText(fileInfo.fileName());
- m_pathValue->setText(QDir::toNativeSeparators(item->Pfile));
+ m_nameText = fileInfo.fileName();
+ m_pathText = QDir::toNativeSeparators(item->Pfile);
}
if (!item->OnMasterPage.isEmpty())
- m_pageValue->setText(tr("Master: %1").arg(item->OnMasterPage));
+ m_pageText = tr("Master %1").arg(item->OnMasterPage);
else if (item->OwnPage < 0)
- m_pageValue->setText(tr("Not on a Page"));
+ m_pageText = tr("Not on a Page");
else
- m_pageValue->setText(QString::number(item->OwnPage + 1));
-
- m_frameValue->setText(item->itemName());
+ m_pageText = tr("Page %1").arg(item->OwnPage + 1);
+ m_frameText = item->itemName();
+ updateElidedLabels();
+
+ m_formatValue->setText(formatText(item));
+ m_frameSizeValue->setText(tr("%1 × %2%3")
+ .arg(item->width() * m_doc->unitRatio(), 0, 'f', 2)
+ .arg(item->height() * m_doc->unitRatio(), 0, 'f', 2)
+ .arg(unitGetSuffixFromIndex(m_doc->unitIndex())));
if (item->imageIsAvailable)
{
+ QString colorSpace;
+ const QString ext = fileInfo.suffix().toLower();
+ if ((extensionIndicatesPDF(ext) || extensionIndicatesEPSorPS(ext)) && item->pixm.imgInfo.type != ImageType7)
+ colorSpace = tr("Unknown");
+ else
+ colorSpace = colorSpaceText(item->pixm.imgInfo.colorspace);
+ m_colorSpaceValue->setText(colorSpace);
m_dimensionsValue->setText(tr("%1 × %2 px").arg(item->OrigW).arg(item->OrigH));
+ m_nativeResolutionValue->setText(tr("%1 × %2 dpi")
+ .arg(item->pixm.imgInfo.xres).arg(item->pixm.imgInfo.yres));
+
if (item->imageXScale() != 0.0 && item->imageYScale() != 0.0)
{
m_resolutionValue->setText(tr("%1 × %2 dpi")
.arg(qRound(72.0 / item->imageXScale()))
.arg(qRound(72.0 / item->imageYScale())));
+ m_scaleValue->setText(tr("%1 × %2 %")
+ .arg(item->imageXScale() * 100.0 / 72.0 * item->pixm.imgInfo.xres, 0, 'f', 1)
+ .arg(item->imageYScale() * 100.0 / 72.0 * item->pixm.imgInfo.yres, 0, 'f', 1));
+ m_scaledImageSizeValue->setText(tr("%1 × %2%3")
+ .arg(item->OrigW * item->imageXScale() * m_doc->unitRatio(), 0, 'f', 2)
+ .arg(item->OrigH * item->imageYScale() * m_doc->unitRatio(), 0, 'f', 2)
+ .arg(unitGetSuffixFromIndex(m_doc->unitIndex())));
}
else
- m_resolutionValue->setText(QString::fromUtf8("—"));
+ {
+ m_resolutionValue->setText(empty);
+ m_scaleValue->setText(empty);
+ m_scaledImageSizeValue->setText(empty);
+ }
}
else
{
- m_dimensionsValue->setText(QString::fromUtf8("—"));
- m_resolutionValue->setText(QString::fromUtf8("—"));
+ m_colorSpaceValue->setText(empty);
+ m_dimensionsValue->setText(empty);
+ m_nativeResolutionValue->setText(empty);
+ m_resolutionValue->setText(empty);
+ m_scaleValue->setText(empty);
+ m_scaledImageSizeValue->setText(empty);
}
+ updatePlacementState(item);
+
if (item->isInlineImage)
m_statusValue->setText(tr("Embedded"));
else if (item->Pfile.isEmpty())
- m_statusValue->setText(tr("Empty"));
+ m_statusValue->setText(tr("Empty Frame"));
else if (item->imageIsAvailable)
m_statusValue->setText(tr("Linked"));
- else
+ else if (!QFileInfo::exists(item->Pfile))
m_statusValue->setText(tr("Missing"));
+ else
+ m_statusValue->setText(tr("Unavailable"));
+
+ {
+ const QSignalBlocker visibleBlocker(m_visibleCheck);
+ const QSignalBlocker printBlocker(m_printCheck);
+ m_visibleCheck->setChecked(item->imageVisible());
+ m_printCheck->setChecked(item->printEnabled());
+ }
+ updateActionState(item);
}
void ImagePalette::syncSelectionFromDocument()
@@ -374,23 +961,231 @@
return;
PageItem* item = m_doc->m_Selection->itemAt(0);
- QModelIndex index = m_model->indexForItem(item);
- if (!index.isValid())
+ selectPaletteItem(item, true);
+}
+
+void ImagePalette::updateActionState(PageItem* item)
+{
+ const bool hasItem = item != nullptr;
+ const bool externalFile = hasItem && !item->isInlineImage && !item->Pfile.isEmpty();
+ const bool available = hasItem && item->imageIsAvailable;
+ const bool raster = available && item->isRaster;
+ const bool missing = externalFile && !available && !QFileInfo::exists(item->Pfile);
+ const bool folderAvailable = externalFile && QDir(QFileInfo(item->Pfile).absolutePath()).exists();
+ const bool canSelect = hasItem && m_ScMW;
+ const bool canReplace = externalFile;
+ const bool canEdit = raster && m_ScMW;
+ const bool canShowProperties = hasItem && m_ScMW;
+
+ m_placementWidget->setVisible(hasItem);
+ m_visibleCheck->setEnabled(hasItem);
+ m_printCheck->setEnabled(hasItem);
+ m_detailsLabel->setVisible(hasItem);
+ m_detailsWidget->setVisible(hasItem);
+
+ m_selectButton->setEnabled(canSelect);
+ m_relinkButton->setEnabled(canReplace);
+ m_searchButton->setEnabled(missing);
+ m_revealButton->setEnabled(folderAvailable);
+ m_editButton->setEnabled(canEdit);
+ m_effectsButton->setEnabled(canEdit);
+ m_propertiesButton->setEnabled(canShowProperties);
+
+ QList<QPushButton*> visibleButtons;
+ if (m_scopeTabs->currentIndex() == 2 && missing)
+ {
+ if (missing)
+ visibleButtons << m_searchButton;
+ if (canReplace)
+ visibleButtons << m_relinkButton;
+ if (canSelect)
+ visibleButtons << m_selectButton;
+ if (folderAvailable)
+ visibleButtons << m_revealButton;
+ if (canShowProperties)
+ visibleButtons << m_propertiesButton;
+ }
+ else
+ {
+ if (canSelect)
+ visibleButtons << m_selectButton;
+ if (canReplace)
+ visibleButtons << m_relinkButton;
+ if (canEdit)
+ visibleButtons << m_editButton;
+ if (folderAvailable)
+ visibleButtons << m_revealButton;
+ if (canEdit)
+ visibleButtons << m_effectsButton;
+ if (canShowProperties)
+ visibleButtons << m_propertiesButton;
+ }
+
+ updateActionLayout(visibleButtons);
+ m_actionsWidget->setVisible(!visibleButtons.isEmpty());
+}
+
+void ImagePalette::updateActionLayout(const QList<QPushButton*>& visibleButtons)
+{
+ if (!m_actionLayout)
+ return;
+
+ while (QLayoutItem* item = m_actionLayout->takeAt(0))
+ delete item;
+
+ const QList<QPushButton*> allButtons { m_searchButton, m_selectButton, m_relinkButton, m_editButton, m_revealButton, m_effectsButton, m_propertiesButton };
+ for (QPushButton* button : allButtons)
+ {
+ if (button)
+ button->hide();
+ }
+
+ for (int i = 0; i < visibleButtons.count(); ++i)
+ {
+ QPushButton* button = visibleButtons.at(i);
+ if (!button)
+ continue;
+
+ int row = i / 2;
+ int column = i % 2;
+ if (i == visibleButtons.count() - 1 && (visibleButtons.count() % 2) == 1
+ && button == m_propertiesButton && m_scopeTabs->currentIndex() == 2)
+ column = 1;
+
+ const Qt::Alignment alignment = column == 0 ? Qt::AlignRight : Qt::AlignLeft;
+ m_actionLayout->addWidget(button, row, column, alignment);
+ button->show();
+ }
+}
+
+void ImagePalette::updateInspectorLabelWidth()
+{
+ const QList<QLabel*> labels {
+ m_statusLabel,
+ m_placementLabel,
+ m_pageLabel,
+ m_frameLabel,
+ m_formatLabel,
+ m_colorSpaceLabel,
+ m_dimensionsLabel,
+ m_nativeResolutionLabel,
+ m_resolutionLabel,
+ m_scaleLabel,
+ m_frameSizeLabel,
+ m_scaledImageSizeLabel
+ };
+
+ int gutterWidth = 0;
+ for (QLabel* label : labels)
+ {
+ if (!label)
+ continue;
+ label->setMinimumWidth(0);
+ gutterWidth = qMax(gutterWidth, label->sizeHint().width());
+ }
+
+ const QList<QPushButton*> buttons { m_searchButton, m_selectButton, m_relinkButton, m_editButton, m_revealButton, m_effectsButton, m_propertiesButton };
+ for (QPushButton* button : buttons)
+ {
+ if (button)
+ gutterWidth = qMax(gutterWidth, button->minimumWidth());
+ }
+
+ for (QLabel* label : labels)
+ {
+ if (!label)
+ continue;
+ label->setMinimumWidth(gutterWidth);
+ label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+ }
+
+ if (m_infoLayout)
+ m_infoLayout->setColumnMinimumWidth(0, gutterWidth);
+ if (m_actionLayout)
+ m_actionLayout->setColumnMinimumWidth(0, gutterWidth);
+ if (m_detailsLayout)
+ m_detailsLayout->setColumnMinimumWidth(0, gutterWidth);
+}
+
+void ImagePalette::updateInspectorMinimumWidth()
+{
+ if (!m_inspector || !m_splitter || !m_imageView || !m_infoLayout || !m_actionLayout)
return;
- const QSignalBlocker blocker(m_imageView->selectionModel());
- m_imageView->setCurrentIndex(index);
- showItem(item);
+ const int gutterWidth = m_infoLayout->columnMinimumWidth(0);
+ const int valueWidth = qMax(qMax(m_visibleCheck->sizeHint().width(), m_printCheck->sizeHint().width()),
+ qMax(m_relinkButton->minimumWidth(), qMax(m_revealButton->minimumWidth(), m_propertiesButton->minimumWidth())));
+ const int columnSpacing = qMax(m_infoLayout->horizontalSpacing(), m_actionLayout->horizontalSpacing());
+ const QMargins margins = m_inspector->layout()->contentsMargins();
+ const int inspectorMinimumWidth = gutterWidth + columnSpacing + valueWidth + margins.left() + margins.right();
+
+ m_inspector->setMinimumWidth(inspectorMinimumWidth);
+
+ const int imageViewMinimumWidth = qMax(m_imageView->minimumWidth(), m_imageView->minimumSizeHint().width());
+ const int splitterMinimumWidth = imageViewMinimumWidth + m_splitter->handleWidth() + inspectorMinimumWidth;
+ m_splitter->setMinimumWidth(splitterMinimumWidth);
+}
+
+void ImagePalette::updateElidedLabels()
+{
+ auto setElidedText = [](QLabel* label, const QString& fullText)
+ {
+ const int availableWidth = qMax(20, label->contentsRect().width());
+ label->setText(label->fontMetrics().elidedText(fullText, Qt::ElideMiddle, availableWidth));
+ label->setToolTip(fullText);
+ };
+
+ setElidedText(m_nameValue, m_nameText);
+ setElidedText(m_pathValue, m_pathText);
+ setElidedText(m_pageValue, m_pageText);
+ setElidedText(m_frameValue, m_frameText);
+}
+
+void ImagePalette::updatePlacementState(PageItem* item)
+{
+ const QVector<PageItem*> placements = m_model->placementsForItem(item);
+ const int count = placements.count();
+ const int current = placements.indexOf(item);
+ if (count <= 0 || current < 0)
+ {
+ m_placementPositionLabel->setText(QString::fromUtf8("—"));
+ m_previousPlacementButton->hide();
+ m_nextPlacementButton->hide();
+ return;
+ }
+
+ m_placementPositionLabel->setText(tr("%1 of %2").arg(current + 1).arg(count));
+ const bool multiple = count > 1;
+ m_previousPlacementButton->setVisible(multiple);
+ m_nextPlacementButton->setVisible(multiple);
+ m_previousPlacementButton->setEnabled(current > 0);
+ m_nextPlacementButton->setEnabled(current + 1 < count);
}
void ImagePalette::updateSummary()
{
+ m_scopeTabs->setTabText(2, tr("Missing (%1)").arg(m_model->missingCount()));
if (!m_doc)
{
- m_summaryLabel->setText(tr("No document"));
+ m_summaryLabel->setText(tr("No document open"));
return;
}
const int count = m_model->rowCount();
- m_summaryLabel->setText(count == 1 ? tr("1 image frame") : tr("%1 image frames").arg(count));
+ if (count == 0)
+ {
+ if (m_scopeTabs->currentIndex() == 2)
+ m_summaryLabel->setText(tr("No missing images"));
+ else if (m_scopeTabs->currentIndex() == 1)
+ m_summaryLabel->setText(tr("No image frames on this page"));
+ else
+ m_summaryLabel->setText(tr("No image frames in this document"));
+ return;
+ }
+
+ const bool selected = m_imageView->currentIndex().isValid();
+ if (count == 1)
+ m_summaryLabel->setText(selected ? tr("1 image frame · 1 selected") : tr("1 image frame"));
+ else
+ m_summaryLabel->setText(selected ? tr("%1 image frames · 1 selected").arg(count) : tr("%1 image frames").arg(count));
}
img3-images-folder-browsing-v1.04.patch (39,015 bytes)
Index: scribus/CMakeLists_Sources.txt
===================================================================
--- scribus/CMakeLists_Sources.txt (revision 27816)
+++ scribus/CMakeLists_Sources.txt (working copy)
@@ -367,7 +367,10 @@
ui/imagelayers.cpp
ui/imageinfodialog.cpp
ui/imagepalette.cpp
+ ui/imagepalette_browse.cpp
ui/imagepalette_documentmodel.cpp
+ ui/imagepalette_filemodel.cpp
+ ui/imagepalette_fileworker.cpp
ui/inlinepalette.cpp
ui/insertTable.cpp
ui/insertaframe.cpp
Index: scribus/ui/imagepalette.cpp
===================================================================
--- scribus/ui/imagepalette.cpp (revision 27816)
+++ scribus/ui/imagepalette.cpp (working copy)
@@ -25,6 +25,7 @@
#include <QSignalBlocker>
#include <QSizePolicy>
#include <QSplitter>
+#include <QStackedWidget>
#include <QStyle>
#include <QTabBar>
#include <QTimer>
@@ -32,6 +33,7 @@
#include <QUrl>
#include <QVBoxLayout>
+#include "imagepalette_browse.h"
#include "imagepalette_documentmodel.h"
#include "pageitem.h"
#include "pageitemiterator.h"
@@ -59,17 +61,33 @@
layout->setContentsMargins(6, 6, 6, 6);
layout->setSpacing(6);
- m_scopeTabs = new QTabBar(panel);
+ m_modeTabs = new QTabBar(panel);
+ m_modeTabs->setDocumentMode(true);
+ m_modeTabs->setExpanding(false);
+ m_modeTabs->addTab(QString());
+ m_modeTabs->addTab(QString());
+ layout->addWidget(m_modeTabs);
+
+ m_modeStack = new QStackedWidget(panel);
+ layout->addWidget(m_modeStack, 1);
+
+ auto* documentPage = new QWidget(m_modeStack);
+ auto* documentLayout = new QVBoxLayout(documentPage);
+ documentLayout->setContentsMargins(0, 0, 0, 0);
+ documentLayout->setSpacing(6);
+ m_modeStack->addWidget(documentPage);
+
+ m_scopeTabs = new QTabBar(documentPage);
m_scopeTabs->setDocumentMode(true);
m_scopeTabs->setExpanding(false);
m_scopeTabs->addTab(QString());
m_scopeTabs->addTab(QString());
m_scopeTabs->addTab(QString());
- layout->addWidget(m_scopeTabs);
+ documentLayout->addWidget(m_scopeTabs);
- m_splitter = new QSplitter(Qt::Horizontal, panel);
+ m_splitter = new QSplitter(Qt::Horizontal, documentPage);
m_splitter->setChildrenCollapsible(false);
- layout->addWidget(m_splitter, 1);
+ documentLayout->addWidget(m_splitter, 1);
m_imageView = new QListView(m_splitter);
m_imageView->setMinimumSize(180, 180);
@@ -243,14 +261,18 @@
m_splitter->setStretchFactor(0, 3);
m_splitter->setStretchFactor(1, 2);
- m_summaryLabel = new QLabel(panel);
- layout->addWidget(m_summaryLabel);
+ m_summaryLabel = new QLabel(documentPage);
+ documentLayout->addWidget(m_summaryLabel);
+
+ m_browsePage = new ImagePaletteBrowse(m_modeStack);
+ m_modeStack->addWidget(m_browsePage);
setWidget(panel);
m_refreshTimer->setSingleShot(true);
m_refreshTimer->setInterval(75);
+ connect(m_modeTabs, &QTabBar::currentChanged, this, &ImagePalette::modeChanged);
connect(m_scopeTabs, &QTabBar::currentChanged, this, &ImagePalette::scopeChanged);
connect(m_imageView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &ImagePalette::handleViewSelectionChanged);
@@ -291,8 +313,9 @@
{
if (doc == static_cast<ScribusDoc*>(m_doc))
{
+ m_browsePage->setDocumentDirectory(documentDirectory());
m_dirty = true;
- if (isVisible())
+ if (isVisible() && m_modeTabs->currentIndex() == 0)
rebuild();
return;
}
@@ -305,6 +328,7 @@
m_doc = doc;
m_model->setDocument(m_doc);
+ m_browsePage->setDocumentDirectory(documentDirectory());
m_syncSelectionAfterRebuild = true;
if (m_doc)
@@ -314,7 +338,7 @@
}
m_dirty = true;
- if (isVisible())
+ if (isVisible() && m_modeTabs->currentIndex() == 0)
rebuild();
else
updateSummary();
@@ -332,6 +356,7 @@
m_syncSelectionAfterRebuild = false;
m_doc = nullptr;
m_model->setDocument(nullptr);
+ m_browsePage->setDocumentDirectory(QString());
m_dirty = false;
clearInspector();
updateSummary();
@@ -350,6 +375,28 @@
updateElidedLabels();
}
+void ImagePalette::modeChanged(int index)
+{
+ if (!m_modeStack)
+ return;
+
+ m_modeStack->setCurrentIndex(index);
+ if (index == 1)
+ {
+ m_refreshTimer->stop();
+ m_browsePage->ensureInitialized(documentDirectory());
+ return;
+ }
+
+ if (m_dirty)
+ rebuild();
+ else
+ {
+ syncSelectionFromDocument();
+ updateSummary();
+ }
+}
+
void ImagePalette::scopeChanged(int index)
{
ImagePaletteDocumentModel::Scope scope = ImagePaletteDocumentModel::Scope::Document;
@@ -386,6 +433,9 @@
void ImagePalette::handleDocumentSelectionChanged()
{
+ if (m_modeTabs->currentIndex() != 0)
+ return;
+
if (m_scopeTabs->currentIndex() == 1)
{
m_syncSelectionAfterRebuild = true;
@@ -410,9 +460,17 @@
void ImagePalette::handleVisibilityChanged(bool visible)
{
- if (!visible || !m_dirty)
+ if (!visible)
+ return;
+
+ if (m_modeTabs->currentIndex() == 1)
+ {
+ m_browsePage->ensureInitialized(documentDirectory());
return;
+ }
+ if (!m_dirty)
+ return;
m_refreshTimer->stop();
rebuild();
}
@@ -611,6 +669,17 @@
updateActionState(nullptr);
}
+QString ImagePalette::documentDirectory() const
+{
+ if (!m_doc || !m_doc->hasName)
+ return QString();
+
+ const QString fileName = m_doc->documentFileName();
+ if (fileName.isEmpty())
+ return QString();
+ return QFileInfo(fileName).absolutePath();
+}
+
PageItem* ImagePalette::currentItem() const
{
return m_model->itemAt(m_imageView->currentIndex());
@@ -648,6 +717,8 @@
void ImagePalette::languageChange()
{
setWindowTitle(tr("Images"));
+ m_modeTabs->setTabText(0, tr("Document"));
+ m_modeTabs->setTabText(1, tr("Browse"));
m_scopeTabs->setTabText(0, tr("Document"));
m_scopeTabs->setTabText(1, tr("Current Page"));
m_scopeTabs->setTabText(2, tr("Missing (%1)").arg(m_model->missingCount()));
@@ -711,7 +782,7 @@
updateInspectorLabelWidth();
updateInspectorMinimumWidth();
m_dirty = true;
- if (isVisible())
+ if (isVisible() && m_modeTabs->currentIndex() == 0)
rebuild();
else
updateSummary();
@@ -821,7 +892,7 @@
void ImagePalette::scheduleRebuild()
{
- if (!isVisible())
+ if (!isVisible() || m_modeTabs->currentIndex() != 0)
return;
m_refreshTimer->start();
}
@@ -1124,6 +1195,7 @@
const int imageViewMinimumWidth = qMax(m_imageView->minimumWidth(), m_imageView->minimumSizeHint().width());
const int splitterMinimumWidth = imageViewMinimumWidth + m_splitter->handleWidth() + inspectorMinimumWidth;
m_splitter->setMinimumWidth(splitterMinimumWidth);
+ m_modeStack->setMinimumWidth(splitterMinimumWidth);
}
void ImagePalette::updateElidedLabels()
Index: scribus/ui/imagepalette.h
===================================================================
--- scribus/ui/imagepalette.h (revision 27816)
+++ scribus/ui/imagepalette.h (working copy)
@@ -22,12 +22,14 @@
class QGridLayout;
class QResizeEvent;
class QSplitter;
+class QStackedWidget;
class QTabBar;
class QTimer;
class QToolButton;
class QWidget;
class QItemSelection;
+class ImagePaletteBrowse;
class ImagePaletteDocumentModel;
class PageItem;
class ScribusDoc;
@@ -52,6 +54,7 @@
void resizeEvent(QResizeEvent* event) override;
private slots:
+ void modeChanged(int index);
void scopeChanged(int index);
void handleDocumentChanged();
void handleDocumentSelectionChanged();
@@ -72,6 +75,7 @@
private:
void clearInspector();
+ QString documentDirectory() const;
PageItem* currentItem() const;
QString formatText(PageItem* item) const;
void languageChange();
@@ -100,6 +104,9 @@
bool m_syncSelectionAfterRebuild {false};
bool m_syncingSelection {false};
+ QTabBar* m_modeTabs {nullptr};
+ QStackedWidget* m_modeStack {nullptr};
+ ImagePaletteBrowse* m_browsePage {nullptr};
QTabBar* m_scopeTabs {nullptr};
QListView* m_imageView {nullptr};
QSplitter* m_splitter {nullptr};
Index: scribus/ui/imagepalette_browse.cpp
===================================================================
--- scribus/ui/imagepalette_browse.cpp (nonexistent)
+++ scribus/ui/imagepalette_browse.cpp (working copy)
@@ -0,0 +1,478 @@
+/*
+For general Scribus (>=1.3.2) copyright and licensing information please refer
+to the COPYING file provided with the program. Following this notice may exist
+a copyright and/or license notice that predates the release of Scribus 1.3.2
+for which a new license (GPL+exception) is in place.
+*/
+
+#include "imagepalette_browse.h"
+
+#include <QAbstractItemView>
+#include <QDir>
+#include <QEvent>
+#include <QFileDialog>
+#include <QFileInfo>
+#include <QGridLayout>
+#include <QHBoxLayout>
+#include <QItemSelectionModel>
+#include <QLabel>
+#include <QLineEdit>
+#include <QListView>
+#include <QResizeEvent>
+#include <QSizePolicy>
+#include <QSplitter>
+#include <QStandardPaths>
+#include <QThread>
+#include <QToolButton>
+#include <QVBoxLayout>
+
+#include "iconmanager.h"
+#include "imagepalette_fileworker.h"
+#include "loadsaveplugin.h"
+#include "plugins/formatidlist.h"
+#include "prefscontext.h"
+#include "prefsfile.h"
+#include "prefsmanager.h"
+#include "util_color.h"
+#include "util_formats.h"
+
+ImagePaletteBrowse::ImagePaletteBrowse(QWidget* parent)
+ : QWidget(parent),
+ m_model(new ImagePaletteFileModel(this))
+{
+ auto* layout = new QVBoxLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setSpacing(6);
+
+ auto* folderLayout = new QHBoxLayout();
+ folderLayout->setContentsMargins(0, 0, 0, 0);
+ m_folderLabel = new QLabel(this);
+ m_folderEdit = new QLineEdit(this);
+ m_folderButton = new QToolButton(this);
+ m_folderButton->setText(QString::fromUtf8("…"));
+ folderLayout->addWidget(m_folderLabel);
+ folderLayout->addWidget(m_folderEdit, 1);
+ folderLayout->addWidget(m_folderButton);
+ layout->addLayout(folderLayout);
+
+ m_splitter = new QSplitter(Qt::Horizontal, this);
+ m_splitter->setChildrenCollapsible(false);
+ layout->addWidget(m_splitter, 1);
+
+ m_imageView = new QListView(m_splitter);
+ m_imageView->setMinimumSize(180, 180);
+ m_imageView->setModel(m_model);
+ m_imageView->setIconSize(QSize(128, 128));
+ m_imageView->setGridSize(QSize(156, 166));
+ m_imageView->setResizeMode(QListView::Adjust);
+ m_imageView->setViewMode(QListView::IconMode);
+ m_imageView->setMovement(QListView::Static);
+ m_imageView->setFlow(QListView::LeftToRight);
+ m_imageView->setWrapping(true);
+ m_imageView->setWordWrap(false);
+ m_imageView->setTextElideMode(Qt::ElideMiddle);
+ m_imageView->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ m_imageView->setSelectionMode(QAbstractItemView::SingleSelection);
+ m_model->setPlaceholderIcon(IconManager::instance().loadIcon("image-collection"));
+
+ m_inspector = new QWidget(m_splitter);
+ auto* inspectorLayout = new QVBoxLayout(m_inspector);
+ inspectorLayout->setContentsMargins(8, 0, 8, 0);
+
+ m_nameValue = new QLabel(m_inspector);
+ QFont nameFont = m_nameValue->font();
+ nameFont.setBold(true);
+ m_nameValue->setFont(nameFont);
+ m_nameValue->setWordWrap(false);
+ m_nameValue->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
+ inspectorLayout->addWidget(m_nameValue);
+
+ m_pathValue = new QLabel(m_inspector);
+ m_pathValue->setWordWrap(false);
+ m_pathValue->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
+ inspectorLayout->addWidget(m_pathValue);
+ inspectorLayout->addStretch(1);
+
+ m_detailsLabel = new QLabel(m_inspector);
+ QFont detailsFont = m_detailsLabel->font();
+ detailsFont.setBold(true);
+ m_detailsLabel->setFont(detailsFont);
+ inspectorLayout->addWidget(m_detailsLabel);
+
+ auto* detailsWidget = new QWidget(m_inspector);
+ m_detailsLayout = new QGridLayout(detailsWidget);
+ m_detailsLayout->setContentsMargins(0, 0, 0, 0);
+ m_detailsLayout->setHorizontalSpacing(12);
+ m_detailsLayout->setColumnStretch(0, 0);
+ m_detailsLayout->setColumnStretch(1, 1);
+
+ m_formatLabel = new QLabel(detailsWidget);
+ m_formatValue = new QLabel(detailsWidget);
+ m_formatValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_detailsLayout->addWidget(m_formatLabel, 0, 0);
+ m_detailsLayout->addWidget(m_formatValue, 0, 1);
+
+ m_colorSpaceLabel = new QLabel(detailsWidget);
+ m_colorSpaceValue = new QLabel(detailsWidget);
+ m_colorSpaceValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_detailsLayout->addWidget(m_colorSpaceLabel, 1, 0);
+ m_detailsLayout->addWidget(m_colorSpaceValue, 1, 1);
+
+ m_dimensionsLabel = new QLabel(detailsWidget);
+ m_dimensionsValue = new QLabel(detailsWidget);
+ m_dimensionsValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_detailsLayout->addWidget(m_dimensionsLabel, 2, 0);
+ m_detailsLayout->addWidget(m_dimensionsValue, 2, 1);
+
+ m_nativeResolutionLabel = new QLabel(detailsWidget);
+ m_nativeResolutionValue = new QLabel(detailsWidget);
+ m_nativeResolutionValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_detailsLayout->addWidget(m_nativeResolutionLabel, 3, 0);
+ m_detailsLayout->addWidget(m_nativeResolutionValue, 3, 1);
+ inspectorLayout->addWidget(detailsWidget);
+ inspectorLayout->addStretch(12);
+
+ m_splitter->setStretchFactor(0, 3);
+ m_splitter->setStretchFactor(1, 2);
+
+ m_summaryLabel = new QLabel(this);
+ layout->addWidget(m_summaryLabel);
+
+ connect(m_folderButton, &QToolButton::clicked, this, &ImagePaletteBrowse::chooseFolder);
+ connect(m_folderEdit, &QLineEdit::returnPressed, this, &ImagePaletteBrowse::folderEntered);
+ connect(m_imageView->selectionModel(), &QItemSelectionModel::currentChanged,
+ this, &ImagePaletteBrowse::currentFileChanged);
+ connect(m_splitter, &QSplitter::splitterMoved, this, [this](int, int) { updateElidedLabels(); });
+
+ languageChange();
+ showFile(QModelIndex());
+ updateSummary();
+}
+
+ImagePaletteBrowse::~ImagePaletteBrowse()
+{
+ if (!m_workerThread)
+ return;
+
+ ++m_generation;
+ if (m_worker)
+ m_worker->setRequestedGeneration(m_generation);
+ m_workerThread->quit();
+ m_workerThread->wait();
+}
+
+void ImagePaletteBrowse::ensureInitialized(const QString& documentDirectory)
+{
+ setDocumentDirectory(documentDirectory);
+ if (m_initialized)
+ return;
+
+ startWorker();
+ m_initialized = true;
+ refreshSupportedExtensions();
+ setFolder(initialFolder());
+}
+
+void ImagePaletteBrowse::setDocumentDirectory(const QString& documentDirectory)
+{
+ m_documentDirectory = documentDirectory;
+}
+
+void ImagePaletteBrowse::changeEvent(QEvent* event)
+{
+ QWidget::changeEvent(event);
+ if (event->type() == QEvent::LanguageChange)
+ languageChange();
+}
+
+void ImagePaletteBrowse::resizeEvent(QResizeEvent* event)
+{
+ QWidget::resizeEvent(event);
+ updateElidedLabels();
+}
+
+void ImagePaletteBrowse::chooseFolder()
+{
+ QString startPath = m_folderPath;
+ if (startPath.isEmpty() || !QDir(startPath).exists())
+ startPath = initialFolder();
+ const QString folderPath = QFileDialog::getExistingDirectory(this, tr("Choose Image Folder"), startPath);
+ if (!folderPath.isEmpty())
+ setFolder(folderPath);
+}
+
+void ImagePaletteBrowse::folderEntered()
+{
+ setFolder(QDir::fromNativeSeparators(m_folderEdit->text().trimmed()));
+}
+
+void ImagePaletteBrowse::currentFileChanged(const QModelIndex& current, const QModelIndex&)
+{
+ showFile(current);
+}
+
+void ImagePaletteBrowse::handleFilesFound(quint64 generation, const QString& folderPath, const QStringList& filePaths)
+{
+ if (generation != m_generation || folderPath != m_folderPath)
+ return;
+
+ m_scanning = false;
+ m_loadingThumbnails = !filePaths.isEmpty();
+ m_model->setFiles(filePaths);
+ if (!filePaths.isEmpty())
+ m_imageView->setCurrentIndex(m_model->index(0, 0));
+ else
+ showFile(QModelIndex());
+ updateSummary();
+}
+
+void ImagePaletteBrowse::handleThumbnailLoaded(quint64 generation, const QString& filePath, const QImage& image,
+ const ImagePaletteFileMetadata& metadata)
+{
+ if (generation != m_generation)
+ return;
+
+ m_model->setThumbnail(filePath, image, metadata);
+ const QModelIndex current = m_imageView->currentIndex();
+ if (m_model->filePathAt(current) == filePath)
+ showFile(current);
+}
+
+void ImagePaletteBrowse::handleLoadingFinished(quint64 generation)
+{
+ if (generation != m_generation)
+ return;
+
+ m_loadingThumbnails = false;
+ updateSummary();
+}
+
+QString ImagePaletteBrowse::initialFolder() const
+{
+ PrefsContext* prefs = PrefsManager::instance().prefsFile->getContext("ImagePalette");
+ const QString savedFolder = prefs->get("browseFolder");
+ if (!savedFolder.isEmpty() && QDir(savedFolder).exists())
+ return savedFolder;
+ if (!m_documentDirectory.isEmpty() && QDir(m_documentDirectory).exists())
+ return m_documentDirectory;
+
+ QString folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
+ if (folder.isEmpty() || !QDir(folder).exists())
+ folder = QDir::homePath();
+ return folder;
+}
+
+QString ImagePaletteBrowse::formatText(const QString& filePath, const ImagePaletteFileMetadata& metadata) const
+{
+ const QString extension = QFileInfo(filePath).suffix().toLower();
+ if (!metadata.valid)
+ return extension.isEmpty() ? tr("Unknown") : extension.toUpper();
+
+ switch (metadata.type)
+ {
+ case 0:
+ return tr("JPEG");
+ case 1:
+ return tr("TIFF");
+ case 2:
+ return tr("PSD");
+ case 3:
+ return tr("EPS/PS");
+ case 4:
+ return tr("PDF");
+ case 5:
+ return tr("JPEG 2000");
+ case 6:
+ return extension.toUpper();
+ case 7:
+ return tr("Embedded PSD");
+ default:
+ return extension.isEmpty() ? tr("Unknown") : extension.toUpper();
+ }
+}
+
+void ImagePaletteBrowse::languageChange()
+{
+ m_folderLabel->setText(tr("Folder:"));
+ m_folderButton->setToolTip(tr("Choose an image folder."));
+ m_folderEdit->setToolTip(tr("Folder whose supported image files are shown below."));
+ m_detailsLabel->setText(tr("Details"));
+ m_formatLabel->setText(tr("Format:"));
+ m_colorSpaceLabel->setText(tr("Color Space:"));
+ m_dimensionsLabel->setText(tr("Pixel Size:"));
+ m_nativeResolutionLabel->setText(tr("Native DPI:"));
+ updateInspectorLabelWidth();
+ showFile(m_imageView->currentIndex());
+ updateSummary();
+}
+
+void ImagePaletteBrowse::refreshSupportedExtensions()
+{
+ m_supportedExtensions.clear();
+ const QString filter = FormatsManager::instance()->extensionListForFormat(FormatsManager::IMAGESIMGFRAME, 0);
+ const QStringList patterns = filter.split(QLatin1Char(' '), Qt::SkipEmptyParts);
+ for (QString pattern : patterns)
+ {
+ pattern = pattern.trimmed().toLower();
+ if (pattern.startsWith(QStringLiteral("*.")))
+ pattern.remove(0, 2);
+ if (!pattern.isEmpty() && !pattern.contains(QLatin1Char('*')))
+ m_supportedExtensions.append(pattern);
+ }
+
+ m_supportedExtensions.append(QStringLiteral("svg"));
+ m_previewExtensions = LoadSavePlugin::getExtensionsForPreview(FORMATID_FIRSTUSER);
+ for (QString& extension : m_previewExtensions)
+ extension = extension.toLower();
+ m_supportedExtensions.append(m_previewExtensions);
+ m_supportedExtensions.removeDuplicates();
+ m_supportedExtensions.sort(Qt::CaseInsensitive);
+}
+
+void ImagePaletteBrowse::setFolder(const QString& folderPath)
+{
+ const QString cleanInput = folderPath.trimmed();
+ const QDir dir(cleanInput);
+ if (cleanInput.isEmpty() || !dir.exists())
+ {
+ ++m_generation;
+ m_worker->setRequestedGeneration(m_generation);
+ m_invalidFolder = true;
+ m_scanning = false;
+ m_loadingThumbnails = false;
+ m_model->clear();
+ showFile(QModelIndex());
+ updateSummary();
+ return;
+ }
+
+ m_invalidFolder = false;
+ m_folderPath = QDir::cleanPath(dir.absolutePath());
+ m_folderEdit->setText(QDir::toNativeSeparators(m_folderPath));
+ PrefsManager::instance().prefsFile->getContext("ImagePalette")->set("browseFolder", m_folderPath);
+
+ ++m_generation;
+ m_worker->setRequestedGeneration(m_generation);
+ m_scanning = true;
+ m_loadingThumbnails = false;
+ m_model->clear();
+ showFile(QModelIndex());
+ updateSummary();
+ emit scanRequested(m_generation, m_folderPath, m_supportedExtensions, m_previewExtensions, m_imageView->iconSize().width());
+}
+
+void ImagePaletteBrowse::startWorker()
+{
+ if (m_workerThread)
+ return;
+
+ qRegisterMetaType<ImagePaletteFileMetadata>("ImagePaletteFileMetadata");
+ m_workerThread = new QThread(this);
+ m_worker = new ImagePaletteFileWorker();
+ m_worker->moveToThread(m_workerThread);
+ connect(m_workerThread, &QThread::finished, m_worker, &QObject::deleteLater);
+ connect(this, &ImagePaletteBrowse::scanRequested, m_worker, &ImagePaletteFileWorker::scanFolder, Qt::QueuedConnection);
+ connect(m_worker, &ImagePaletteFileWorker::filesFound, this, &ImagePaletteBrowse::handleFilesFound, Qt::QueuedConnection);
+ connect(m_worker, &ImagePaletteFileWorker::thumbnailLoaded, this, &ImagePaletteBrowse::handleThumbnailLoaded, Qt::QueuedConnection);
+ connect(m_worker, &ImagePaletteFileWorker::loadingFinished, this, &ImagePaletteBrowse::handleLoadingFinished, Qt::QueuedConnection);
+ m_workerThread->start();
+}
+
+void ImagePaletteBrowse::showFile(const QModelIndex& index)
+{
+ const QString empty = QString::fromUtf8("—");
+ const QString filePath = m_model->filePathAt(index);
+ if (filePath.isEmpty())
+ {
+ m_nameText = empty;
+ m_pathText = empty;
+ m_formatValue->setText(empty);
+ m_colorSpaceValue->setText(empty);
+ m_dimensionsValue->setText(empty);
+ m_nativeResolutionValue->setText(empty);
+ updateElidedLabels();
+ return;
+ }
+
+ const QFileInfo fileInfo(filePath);
+ m_nameText = fileInfo.fileName();
+ m_pathText = QDir::toNativeSeparators(filePath);
+ updateElidedLabels();
+
+ const ImagePaletteFileMetadata metadata = m_model->metadataAt(index);
+ m_formatValue->setText(formatText(filePath, metadata));
+ if (!m_model->metadataReadyAt(index) || !metadata.valid)
+ {
+ m_colorSpaceValue->setText(empty);
+ m_dimensionsValue->setText(empty);
+ m_nativeResolutionValue->setText(empty);
+ return;
+ }
+
+ const QString extension = fileInfo.suffix().toLower();
+ if (metadata.colorSpace < 0 || ((extensionIndicatesPDF(extension) || extensionIndicatesEPSorPS(extension)) && metadata.type != 7))
+ m_colorSpaceValue->setText(tr("Unknown"));
+ else
+ m_colorSpaceValue->setText(colorSpaceText(metadata.colorSpace));
+ m_dimensionsValue->setText(metadata.width > 0 && metadata.height > 0
+ ? tr("%1 × %2 px").arg(metadata.width).arg(metadata.height) : empty);
+ m_nativeResolutionValue->setText(metadata.xResolution > 0 && metadata.yResolution > 0
+ ? tr("%1 × %2 dpi").arg(metadata.xResolution).arg(metadata.yResolution) : empty);
+}
+
+void ImagePaletteBrowse::updateElidedLabels()
+{
+ auto setElidedText = [](QLabel* label, const QString& fullText)
+ {
+ const int availableWidth = qMax(20, label->contentsRect().width());
+ label->setText(label->fontMetrics().elidedText(fullText, Qt::ElideMiddle, availableWidth));
+ label->setToolTip(fullText);
+ };
+
+ setElidedText(m_nameValue, m_nameText);
+ setElidedText(m_pathValue, m_pathText);
+}
+
+void ImagePaletteBrowse::updateInspectorLabelWidth()
+{
+ const QList<QLabel*> labels { m_formatLabel, m_colorSpaceLabel, m_dimensionsLabel, m_nativeResolutionLabel };
+ int gutterWidth = 0;
+ for (QLabel* label : labels)
+ {
+ label->setMinimumWidth(0);
+ gutterWidth = qMax(gutterWidth, label->sizeHint().width());
+ }
+ for (QLabel* label : labels)
+ {
+ label->setMinimumWidth(gutterWidth);
+ label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+ }
+ m_detailsLayout->setColumnMinimumWidth(0, gutterWidth);
+}
+
+void ImagePaletteBrowse::updateSummary()
+{
+ if (m_invalidFolder)
+ {
+ m_summaryLabel->setText(tr("Folder not found"));
+ return;
+ }
+ if (m_scanning)
+ {
+ m_summaryLabel->setText(tr("Scanning folder…"));
+ return;
+ }
+
+ const int count = m_model->rowCount();
+ if (count == 0)
+ {
+ m_summaryLabel->setText(m_folderPath.isEmpty() ? tr("Choose a folder") : tr("No supported images in this folder"));
+ return;
+ }
+ if (m_loadingThumbnails)
+ {
+ m_summaryLabel->setText(tr("%1 images · loading thumbnails…").arg(count));
+ return;
+ }
+ m_summaryLabel->setText(count == 1 ? tr("1 image") : tr("%1 images").arg(count));
+}
Index: scribus/ui/imagepalette_browse.h
===================================================================
--- scribus/ui/imagepalette_browse.h (nonexistent)
+++ scribus/ui/imagepalette_browse.h (working copy)
@@ -0,0 +1,107 @@
+/*
+For general Scribus (>=1.3.2) copyright and licensing information please refer
+to the COPYING file provided with the program. Following this notice may exist
+a copyright and/or license notice that predates the release of Scribus 1.3.2
+for which a new license (GPL+exception) is in place.
+*/
+
+#ifndef IMAGEPALETTE_BROWSE_H
+#define IMAGEPALETTE_BROWSE_H
+
+#include <QImage>
+#include <QString>
+#include <QStringList>
+#include <QWidget>
+
+#include "imagepalette_filemodel.h"
+
+class QEvent;
+class QLabel;
+class QGridLayout;
+class QLineEdit;
+class QListView;
+class QModelIndex;
+class QResizeEvent;
+class QSplitter;
+class QThread;
+class QToolButton;
+
+class ImagePaletteFileWorker;
+
+class ImagePaletteBrowse : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit ImagePaletteBrowse(QWidget* parent = nullptr);
+ ~ImagePaletteBrowse() override;
+
+ void ensureInitialized(const QString& documentDirectory);
+ void setDocumentDirectory(const QString& documentDirectory);
+
+signals:
+ void scanRequested(quint64 generation, const QString& folderPath, const QStringList& supportedExtensions,
+ const QStringList& previewExtensions, int thumbnailSize);
+
+protected:
+ void changeEvent(QEvent* event) override;
+ void resizeEvent(QResizeEvent* event) override;
+
+private slots:
+ void chooseFolder();
+ void folderEntered();
+ void currentFileChanged(const QModelIndex& current, const QModelIndex& previous);
+ void handleFilesFound(quint64 generation, const QString& folderPath, const QStringList& filePaths);
+ void handleThumbnailLoaded(quint64 generation, const QString& filePath, const QImage& image,
+ const ImagePaletteFileMetadata& metadata);
+ void handleLoadingFinished(quint64 generation);
+
+private:
+ QString initialFolder() const;
+ QString formatText(const QString& filePath, const ImagePaletteFileMetadata& metadata) const;
+ void languageChange();
+ void refreshSupportedExtensions();
+ void setFolder(const QString& folderPath);
+ void showFile(const QModelIndex& index);
+ void startWorker();
+ void updateElidedLabels();
+ void updateInspectorLabelWidth();
+ void updateSummary();
+
+ ImagePaletteFileModel* m_model {nullptr};
+ ImagePaletteFileWorker* m_worker {nullptr};
+ QThread* m_workerThread {nullptr};
+ quint64 m_generation {0};
+ bool m_initialized {false};
+ bool m_scanning {false};
+ bool m_loadingThumbnails {false};
+ bool m_invalidFolder {false};
+ QString m_documentDirectory;
+ QString m_folderPath;
+ QStringList m_supportedExtensions;
+ QStringList m_previewExtensions;
+
+ QLabel* m_folderLabel {nullptr};
+ QLineEdit* m_folderEdit {nullptr};
+ QToolButton* m_folderButton {nullptr};
+ QSplitter* m_splitter {nullptr};
+ QListView* m_imageView {nullptr};
+ QWidget* m_inspector {nullptr};
+ QLabel* m_nameValue {nullptr};
+ QLabel* m_pathValue {nullptr};
+ QString m_nameText;
+ QString m_pathText;
+ QLabel* m_detailsLabel {nullptr};
+ QGridLayout* m_detailsLayout {nullptr};
+ QLabel* m_formatLabel {nullptr};
+ QLabel* m_formatValue {nullptr};
+ QLabel* m_colorSpaceLabel {nullptr};
+ QLabel* m_colorSpaceValue {nullptr};
+ QLabel* m_dimensionsLabel {nullptr};
+ QLabel* m_dimensionsValue {nullptr};
+ QLabel* m_nativeResolutionLabel {nullptr};
+ QLabel* m_nativeResolutionValue {nullptr};
+ QLabel* m_summaryLabel {nullptr};
+};
+
+#endif // IMAGEPALETTE_BROWSE_H
Index: scribus/ui/imagepalette_filemodel.cpp
===================================================================
--- scribus/ui/imagepalette_filemodel.cpp (nonexistent)
+++ scribus/ui/imagepalette_filemodel.cpp (working copy)
@@ -0,0 +1,116 @@
+/*
+For general Scribus (>=1.3.2) copyright and licensing information please refer
+to the COPYING file provided with the program. Following this notice may exist
+a copyright and/or license notice that predates the release of Scribus 1.3.2
+for which a new license (GPL+exception) is in place.
+*/
+
+#include "imagepalette_filemodel.h"
+
+#include <QDir>
+#include <QFileInfo>
+#include <QImage>
+#include <QPixmap>
+
+ImagePaletteFileModel::ImagePaletteFileModel(QObject* parent)
+ : QAbstractListModel(parent)
+{
+}
+
+int ImagePaletteFileModel::rowCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return m_entries.count();
+}
+
+QVariant ImagePaletteFileModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.count())
+ return QVariant();
+
+ const Entry& entry = m_entries.at(index.row());
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ return entry.displayName;
+ case Qt::DecorationRole:
+ return entry.thumbnail.isNull() ? m_placeholderIcon : entry.thumbnail;
+ case Qt::ToolTipRole:
+ return QDir::toNativeSeparators(entry.filePath);
+ default:
+ break;
+ }
+ return QVariant();
+}
+
+void ImagePaletteFileModel::clear()
+{
+ beginResetModel();
+ m_entries.clear();
+ m_rowByPath.clear();
+ endResetModel();
+}
+
+void ImagePaletteFileModel::setFiles(const QStringList& filePaths)
+{
+ beginResetModel();
+ m_entries.clear();
+ m_rowByPath.clear();
+ m_entries.reserve(filePaths.count());
+ for (const QString& filePath : filePaths)
+ {
+ Entry entry;
+ entry.filePath = filePath;
+ entry.displayName = QFileInfo(filePath).fileName();
+ m_rowByPath.insert(filePath, m_entries.count());
+ m_entries.append(entry);
+ }
+ endResetModel();
+}
+
+void ImagePaletteFileModel::setPlaceholderIcon(const QIcon& icon)
+{
+ m_placeholderIcon = icon;
+ if (!m_entries.isEmpty())
+ emit dataChanged(index(0, 0), index(m_entries.count() - 1, 0), { Qt::DecorationRole });
+}
+
+void ImagePaletteFileModel::setThumbnail(const QString& filePath, const QImage& image, const ImagePaletteFileMetadata& metadata)
+{
+ const int row = rowForPath(filePath);
+ if (row < 0)
+ return;
+
+ Entry& entry = m_entries[row];
+ entry.thumbnail = image.isNull() ? QIcon() : QIcon(QPixmap::fromImage(image));
+ entry.metadata = metadata;
+ entry.metadataReady = true;
+ emit dataChanged(index(row, 0), index(row, 0), { Qt::DecorationRole });
+}
+
+QString ImagePaletteFileModel::filePathAt(const QModelIndex& index) const
+{
+ if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.count())
+ return QString();
+ return m_entries.at(index.row()).filePath;
+}
+
+ImagePaletteFileMetadata ImagePaletteFileModel::metadataAt(const QModelIndex& index) const
+{
+ if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.count())
+ return ImagePaletteFileMetadata();
+ return m_entries.at(index.row()).metadata;
+}
+
+bool ImagePaletteFileModel::metadataReadyAt(const QModelIndex& index) const
+{
+ if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.count())
+ return false;
+ return m_entries.at(index.row()).metadataReady;
+}
+
+int ImagePaletteFileModel::rowForPath(const QString& filePath) const
+{
+ return m_rowByPath.value(filePath, -1);
+}
Index: scribus/ui/imagepalette_filemodel.h
===================================================================
--- scribus/ui/imagepalette_filemodel.h (nonexistent)
+++ scribus/ui/imagepalette_filemodel.h (working copy)
@@ -0,0 +1,69 @@
+/*
+For general Scribus (>=1.3.2) copyright and licensing information please refer
+to the COPYING file provided with the program. Following this notice may exist
+a copyright and/or license notice that predates the release of Scribus 1.3.2
+for which a new license (GPL+exception) is in place.
+*/
+
+#ifndef IMAGEPALETTE_FILEMODEL_H
+#define IMAGEPALETTE_FILEMODEL_H
+
+#include <QAbstractListModel>
+#include <QHash>
+#include <QIcon>
+#include <QMetaType>
+#include <QStringList>
+#include <QVector>
+
+class QImage;
+
+struct ImagePaletteFileMetadata
+{
+ int width {0};
+ int height {0};
+ int type {-1};
+ int colorSpace {-1};
+ int xResolution {0};
+ int yResolution {0};
+ bool valid {false};
+};
+
+Q_DECLARE_METATYPE(ImagePaletteFileMetadata)
+
+class ImagePaletteFileModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ explicit ImagePaletteFileModel(QObject* parent = nullptr);
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+
+ void clear();
+ void setFiles(const QStringList& filePaths);
+ void setPlaceholderIcon(const QIcon& icon);
+ void setThumbnail(const QString& filePath, const QImage& image, const ImagePaletteFileMetadata& metadata);
+
+ QString filePathAt(const QModelIndex& index) const;
+ ImagePaletteFileMetadata metadataAt(const QModelIndex& index) const;
+ bool metadataReadyAt(const QModelIndex& index) const;
+
+private:
+ struct Entry
+ {
+ QString filePath;
+ QString displayName;
+ QIcon thumbnail;
+ ImagePaletteFileMetadata metadata;
+ bool metadataReady {false};
+ };
+
+ int rowForPath(const QString& filePath) const;
+
+ QIcon m_placeholderIcon;
+ QHash<QString, int> m_rowByPath;
+ QVector<Entry> m_entries;
+};
+
+#endif // IMAGEPALETTE_FILEMODEL_H
Index: scribus/ui/imagepalette_fileworker.cpp
===================================================================
--- scribus/ui/imagepalette_fileworker.cpp (nonexistent)
+++ scribus/ui/imagepalette_fileworker.cpp (working copy)
@@ -0,0 +1,129 @@
+/*
+For general Scribus (>=1.3.2) copyright and licensing information please refer
+to the COPYING file provided with the program. Following this notice may exist
+a copyright and/or license notice that predates the release of Scribus 1.3.2
+for which a new license (GPL+exception) is in place.
+*/
+
+#include "imagepalette_fileworker.h"
+
+#include <utility>
+
+#include <QDir>
+#include <QFileInfo>
+#include <QImage>
+
+#include "cmsettings.h"
+#include "fileloader.h"
+#include "loadsaveplugin.h"
+#include "scimage.h"
+
+ImagePaletteFileWorker::ImagePaletteFileWorker(QObject* parent)
+ : QObject(parent)
+{
+}
+
+void ImagePaletteFileWorker::setRequestedGeneration(quint64 generation)
+{
+ m_requestedGeneration.store(generation, std::memory_order_relaxed);
+}
+
+void ImagePaletteFileWorker::scanFolder(quint64 generation, const QString& folderPath,
+ const QStringList& supportedExtensions, const QStringList& previewExtensions, int thumbnailSize)
+{
+ if (!isCurrent(generation))
+ return;
+
+ QDir dir(folderPath);
+ QStringList filePaths;
+ if (dir.exists())
+ {
+ const QFileInfoList entries = dir.entryInfoList(QDir::Files | QDir::Readable | QDir::NoSymLinks,
+ QDir::Name | QDir::IgnoreCase);
+ for (const QFileInfo& fileInfo : entries)
+ {
+ if (!isCurrent(generation))
+ return;
+ if (supportedExtensions.contains(fileInfo.suffix().toLower()))
+ filePaths.append(fileInfo.absoluteFilePath());
+ }
+ }
+
+ if (!isCurrent(generation))
+ return;
+ emit filesFound(generation, folderPath, filePaths);
+
+ for (const QString& filePath : std::as_const(filePaths))
+ {
+ if (!isCurrent(generation))
+ return;
+
+ QImage thumbnail;
+ ImagePaletteFileMetadata metadata;
+ loadThumbnail(filePath, previewExtensions, thumbnailSize, thumbnail, metadata);
+ if (!isCurrent(generation))
+ return;
+ emit thumbnailLoaded(generation, filePath, thumbnail, metadata);
+ }
+
+ if (isCurrent(generation))
+ emit loadingFinished(generation);
+}
+
+bool ImagePaletteFileWorker::isCurrent(quint64 generation) const
+{
+ return m_requestedGeneration.load(std::memory_order_relaxed) == generation;
+}
+
+void ImagePaletteFileWorker::loadThumbnail(const QString& filePath, const QStringList& previewExtensions,
+ int thumbnailSize, QImage& thumbnail, ImagePaletteFileMetadata& metadata) const
+{
+ const QString extension = QFileInfo(filePath).suffix().toLower();
+ if (previewExtensions.contains(extension))
+ {
+ FileLoader fileLoader(filePath);
+ const int formatId = fileLoader.testFile();
+ if (formatId >= FORMATID_FIRSTUSER)
+ {
+ const FileFormat* format = LoadSavePlugin::getFormatById(formatId);
+ if (format)
+ {
+ QImage image = format->readThumbnail(filePath);
+ if (!image.isNull())
+ {
+ metadata.width = qRound(image.text(QStringLiteral("XSize")).toDouble());
+ metadata.height = qRound(image.text(QStringLiteral("YSize")).toDouble());
+ metadata.type = 6;
+ metadata.valid = true;
+ thumbnail = image.scaled(thumbnailSize, thumbnailSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ return;
+ }
+ }
+ }
+ }
+
+ ScImage image;
+ bool realCMYK = false;
+ CMSettings cms(nullptr, "", Intent_Perceptual);
+ cms.allowColorManagement(false);
+ cms.setUseEmbeddedProfile(true);
+ if (!image.loadPicture(filePath, 1, cms, ScImage::Thumbnail, 72, &realCMYK))
+ return;
+
+ if (image.imgInfo.exifDataValid && !image.imgInfo.exifInfo.thumbnail.isNull())
+ {
+ metadata.width = image.imgInfo.exifInfo.width;
+ metadata.height = image.imgInfo.exifInfo.height;
+ }
+ else
+ {
+ metadata.width = image.width();
+ metadata.height = image.height();
+ }
+ metadata.type = image.imgInfo.type;
+ metadata.colorSpace = image.imgInfo.colorspace;
+ metadata.xResolution = image.imgInfo.xres;
+ metadata.yResolution = image.imgInfo.yres;
+ metadata.valid = true;
+ thumbnail = image.scaled(thumbnailSize, thumbnailSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+}
Index: scribus/ui/imagepalette_fileworker.h
===================================================================
--- scribus/ui/imagepalette_fileworker.h (nonexistent)
+++ scribus/ui/imagepalette_fileworker.h (working copy)
@@ -0,0 +1,47 @@
+/*
+For general Scribus (>=1.3.2) copyright and licensing information please refer
+to the COPYING file provided with the program. Following this notice may exist
+a copyright and/or license notice that predates the release of Scribus 1.3.2
+for which a new license (GPL+exception) is in place.
+*/
+
+#ifndef IMAGEPALETTE_FILEWORKER_H
+#define IMAGEPALETTE_FILEWORKER_H
+
+#include <atomic>
+
+#include <QImage>
+#include <QObject>
+#include <QStringList>
+
+#include "imagepalette_filemodel.h"
+
+class ImagePaletteFileWorker : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit ImagePaletteFileWorker(QObject* parent = nullptr);
+
+ // Thread-safe cancellation token used by the GUI thread before queuing a new scan.
+ void setRequestedGeneration(quint64 generation);
+
+public slots:
+ void scanFolder(quint64 generation, const QString& folderPath, const QStringList& supportedExtensions,
+ const QStringList& previewExtensions, int thumbnailSize);
+
+signals:
+ void filesFound(quint64 generation, const QString& folderPath, const QStringList& filePaths);
+ void thumbnailLoaded(quint64 generation, const QString& filePath, const QImage& image,
+ const ImagePaletteFileMetadata& metadata);
+ void loadingFinished(quint64 generation);
+
+private:
+ bool isCurrent(quint64 generation) const;
+ void loadThumbnail(const QString& filePath, const QStringList& previewExtensions, int thumbnailSize,
+ QImage& thumbnail, ImagePaletteFileMetadata& metadata) const;
+
+ std::atomic<quint64> m_requestedGeneration {0};
+};
+
+#endif // IMAGEPALETTE_FILEWORKER_H
img4-images-browse-drag-drop-v1.06.patch (8,900 bytes)
Index: scribus/ui/imagepalette_browse.cpp
===================================================================
--- scribus/ui/imagepalette_browse.cpp (revision 27816)
+++ scribus/ui/imagepalette_browse.cpp (working copy)
@@ -18,6 +18,7 @@
#include <QLabel>
#include <QLineEdit>
#include <QListView>
+#include <QMargins>
#include <QResizeEvent>
#include <QSizePolicy>
#include <QSplitter>
@@ -72,7 +73,9 @@
m_imageView->setWordWrap(false);
m_imageView->setTextElideMode(Qt::ElideMiddle);
m_imageView->setEditTriggers(QAbstractItemView::NoEditTriggers);
- m_imageView->setSelectionMode(QAbstractItemView::SingleSelection);
+ m_imageView->setSelectionMode(QAbstractItemView::ExtendedSelection);
+ m_imageView->setDragEnabled(true);
+ m_imageView->setDragDropMode(QAbstractItemView::DragOnly);
m_model->setPlaceholderIcon(IconManager::instance().loadIcon("image-collection"));
m_inspector = new QWidget(m_splitter);
@@ -109,24 +112,28 @@
m_formatLabel = new QLabel(detailsWidget);
m_formatValue = new QLabel(detailsWidget);
m_formatValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_formatValue->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
m_detailsLayout->addWidget(m_formatLabel, 0, 0);
m_detailsLayout->addWidget(m_formatValue, 0, 1);
m_colorSpaceLabel = new QLabel(detailsWidget);
m_colorSpaceValue = new QLabel(detailsWidget);
m_colorSpaceValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_colorSpaceValue->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
m_detailsLayout->addWidget(m_colorSpaceLabel, 1, 0);
m_detailsLayout->addWidget(m_colorSpaceValue, 1, 1);
m_dimensionsLabel = new QLabel(detailsWidget);
m_dimensionsValue = new QLabel(detailsWidget);
m_dimensionsValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_dimensionsValue->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
m_detailsLayout->addWidget(m_dimensionsLabel, 2, 0);
m_detailsLayout->addWidget(m_dimensionsValue, 2, 1);
m_nativeResolutionLabel = new QLabel(detailsWidget);
m_nativeResolutionValue = new QLabel(detailsWidget);
m_nativeResolutionValue->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_nativeResolutionValue->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
m_detailsLayout->addWidget(m_nativeResolutionLabel, 3, 0);
m_detailsLayout->addWidget(m_nativeResolutionValue, 3, 1);
inspectorLayout->addWidget(detailsWidget);
@@ -142,6 +149,8 @@
connect(m_folderEdit, &QLineEdit::returnPressed, this, &ImagePaletteBrowse::folderEntered);
connect(m_imageView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &ImagePaletteBrowse::currentFileChanged);
+ connect(m_imageView->selectionModel(), &QItemSelectionModel::selectionChanged,
+ this, [this] { updateSelectionInspector(); });
connect(m_splitter, &QSplitter::splitterMoved, this, [this](int, int) { updateElidedLabels(); });
languageChange();
@@ -206,9 +215,9 @@
setFolder(QDir::fromNativeSeparators(m_folderEdit->text().trimmed()));
}
-void ImagePaletteBrowse::currentFileChanged(const QModelIndex& current, const QModelIndex&)
-{
- showFile(current);
+void ImagePaletteBrowse::currentFileChanged(const QModelIndex&, const QModelIndex&)
+{
+ updateSelectionInspector();
}
void ImagePaletteBrowse::handleFilesFound(quint64 generation, const QString& folderPath, const QStringList& filePaths)
@@ -235,7 +244,7 @@
m_model->setThumbnail(filePath, image, metadata);
const QModelIndex current = m_imageView->currentIndex();
if (m_model->filePathAt(current) == filePath)
- showFile(current);
+ updateSelectionInspector();
}
void ImagePaletteBrowse::handleLoadingFinished(quint64 generation)
@@ -302,7 +311,8 @@
m_dimensionsLabel->setText(tr("Pixel Size:"));
m_nativeResolutionLabel->setText(tr("Native DPI:"));
updateInspectorLabelWidth();
- showFile(m_imageView->currentIndex());
+ updateInspectorMinimumWidth();
+ updateSelectionInspector();
updateSummary();
}
@@ -433,6 +443,25 @@
setElidedText(m_pathValue, m_pathText);
}
+void ImagePaletteBrowse::updateSelectionInspector()
+{
+ const QModelIndexList selectedIndexes = m_imageView->selectionModel()->selectedIndexes();
+ if (selectedIndexes.count() <= 1)
+ {
+ showFile(selectedIndexes.isEmpty() ? QModelIndex() : selectedIndexes.constFirst());
+ return;
+ }
+
+ const QString empty = QString::fromUtf8("—");
+ m_nameText = tr("%n files selected", nullptr, selectedIndexes.count());
+ m_pathText = QDir::toNativeSeparators(m_folderPath);
+ m_formatValue->setText(empty);
+ m_colorSpaceValue->setText(empty);
+ m_dimensionsValue->setText(empty);
+ m_nativeResolutionValue->setText(empty);
+ updateElidedLabels();
+}
+
void ImagePaletteBrowse::updateInspectorLabelWidth()
{
const QList<QLabel*> labels { m_formatLabel, m_colorSpaceLabel, m_dimensionsLabel, m_nativeResolutionLabel };
@@ -450,6 +479,26 @@
m_detailsLayout->setColumnMinimumWidth(0, gutterWidth);
}
+void ImagePaletteBrowse::updateInspectorMinimumWidth()
+{
+ if (!m_inspector || !m_splitter || !m_imageView || !m_detailsLayout)
+ return;
+
+ const int gutterWidth = m_detailsLayout->columnMinimumWidth(0);
+ int valueWidth = m_formatValue->fontMetrics().horizontalAdvance(tr("JPEG 2000"));
+ valueWidth = qMax(valueWidth, m_colorSpaceValue->fontMetrics().horizontalAdvance(tr("Unknown")));
+ valueWidth = qMax(valueWidth, m_dimensionsValue->fontMetrics().horizontalAdvance(QStringLiteral("99999 × 99999 px")));
+ valueWidth = qMax(valueWidth, m_nativeResolutionValue->fontMetrics().horizontalAdvance(QStringLiteral("9999 × 9999 dpi")));
+ const int columnSpacing = qMax(0, m_detailsLayout->horizontalSpacing());
+ const QMargins margins = m_inspector->layout()->contentsMargins();
+ const int inspectorMinimumWidth = gutterWidth + columnSpacing + valueWidth + margins.left() + margins.right();
+
+ m_inspector->setMinimumWidth(inspectorMinimumWidth);
+
+ const int imageViewMinimumWidth = qMax(m_imageView->minimumWidth(), m_imageView->minimumSizeHint().width());
+ m_splitter->setMinimumWidth(imageViewMinimumWidth + m_splitter->handleWidth() + inspectorMinimumWidth);
+}
+
void ImagePaletteBrowse::updateSummary()
{
if (m_invalidFolder)
Index: scribus/ui/imagepalette_browse.h
===================================================================
--- scribus/ui/imagepalette_browse.h (revision 27816)
+++ scribus/ui/imagepalette_browse.h (working copy)
@@ -66,6 +66,8 @@
void startWorker();
void updateElidedLabels();
void updateInspectorLabelWidth();
+ void updateInspectorMinimumWidth();
+ void updateSelectionInspector();
void updateSummary();
ImagePaletteFileModel* m_model {nullptr};
Index: scribus/ui/imagepalette_filemodel.cpp
===================================================================
--- scribus/ui/imagepalette_filemodel.cpp (revision 27816)
+++ scribus/ui/imagepalette_filemodel.cpp (working copy)
@@ -10,7 +10,9 @@
#include <QDir>
#include <QFileInfo>
#include <QImage>
+#include <QMimeData>
#include <QPixmap>
+#include <QUrl>
ImagePaletteFileModel::ImagePaletteFileModel(QObject* parent)
: QAbstractListModel(parent)
@@ -42,6 +44,41 @@
break;
}
return QVariant();
+}
+
+Qt::ItemFlags ImagePaletteFileModel::flags(const QModelIndex& index) const
+{
+ Qt::ItemFlags itemFlags = QAbstractListModel::flags(index);
+ if (index.isValid())
+ itemFlags |= Qt::ItemIsDragEnabled;
+ return itemFlags;
+}
+
+QStringList ImagePaletteFileModel::mimeTypes() const
+{
+ return { QStringLiteral("text/uri-list") };
+}
+
+QMimeData* ImagePaletteFileModel::mimeData(const QModelIndexList& indexes) const
+{
+ auto* mimeData = new QMimeData();
+ QList<QUrl> urls;
+ urls.reserve(indexes.count());
+
+ for (const QModelIndex& index : indexes)
+ {
+ const QString filePath = filePathAt(index);
+ if (!filePath.isEmpty())
+ urls.append(QUrl::fromLocalFile(filePath));
+ }
+
+ mimeData->setUrls(urls);
+ return mimeData;
+}
+
+Qt::DropActions ImagePaletteFileModel::supportedDragActions() const
+{
+ return Qt::CopyAction;
}
void ImagePaletteFileModel::clear()
Index: scribus/ui/imagepalette_filemodel.h
===================================================================
--- scribus/ui/imagepalette_filemodel.h (revision 27816)
+++ scribus/ui/imagepalette_filemodel.h (working copy)
@@ -16,6 +16,7 @@
#include <QVector>
class QImage;
+class QMimeData;
struct ImagePaletteFileMetadata
{
@@ -39,6 +40,10 @@
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+ Qt::ItemFlags flags(const QModelIndex& index) const override;
+ QStringList mimeTypes() const override;
+ QMimeData* mimeData(const QModelIndexList& indexes) const override;
+ Qt::DropActions supportedDragActions() const override;
void clear();
void setFiles(const QStringList& filePaths);
| ||||
| Patch | Yes | ||||
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-09-05 10:19 | buttar | New Issue | |
| 2026-09-05 10:19 | buttar | Tag Attached: #please_test | |
| 2026-09-05 10:19 | buttar | File Added: img1-images-palette-foundation-v1.03.patch | |
| 2026-09-05 10:19 | buttar | File Added: img2-images-document-management-v1.33.patch | |
| 2026-09-05 10:19 | buttar | File Added: img3-images-folder-browsing-v1.04.patch | |
| 2026-09-05 10:19 | buttar | File Added: img4-images-browse-drag-drop-v1.06.patch | |
| 2026-09-05 12:16 | buttar | Note Added: 0054421 | |
| 2026-09-05 12:16 | buttar | File Added: images-palette-browser.png | |
| 2026-09-05 12:16 | buttar | File Added: images-palette-info-and-go-to-frame.png | |
| 2026-09-05 12:16 | buttar | File Added: images-palette-missing.png |