src/gui.cc

Wed, 08 Jan 2014 13:57:10 +0200

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Wed, 08 Jan 2014 13:57:10 +0200
changeset 608
487db37f0bb3
parent 606
3dd6f343ec06
child 611
6679e47b019f
permissions
-rw-r--r--

- if loading another file to replace an explicitly loaded file, this file won't get closed automatically and thus needs to be manually closed. We also need to check that it's safe to close before doing this. Also fixed a rather argh problem with ::save not using the proper path...

/*
 *  LDForge: LDraw parts authoring CAD
 *  Copyright (C) 2013, 2014 Santeri Piippo
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <QGridLayout>
#include <QMessageBox>
#include <QEvent>
#include <QContextMenuEvent>
#include <QMenuBar>
#include <QStatusBar>
#include <QSplitter>
#include <QListWidget>
#include <QToolButton>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QToolBar>
#include <QProgressBar>
#include <QLabel>
#include <QFileDialog>
#include <QPushButton>
#include <QCoreApplication>
#include <QTimer>
#include <QMetaMethod>

#include "main.h"
#include "gldraw.h"
#include "gui.h"
#include "document.h"
#include "config.h"
#include "misc.h"
#include "colors.h"
#include "history.h"
#include "widgets.h"
#include "addObjectDialog.h"
#include "messagelog.h"
#include "config.h"
#include "ui_ldforge.h"
#include "moc_gui.cpp"

static bool g_isSelectionLocked = false;

cfg (Bool, lv_colorize, true);
cfg (String, gui_colortoolbar, "16:24:|:1:2:4:14:0:15:|:33:34:36:46");
cfg (Bool, gui_implicitfiles, false);
extern_cfg (List,		io_recentfiles);
extern_cfg (Bool,		gl_axes);
extern_cfg (String,	gl_maincolor);
extern_cfg (Float,	gl_maincolor_alpha);
extern_cfg (Bool,		gl_wireframe);
extern_cfg (Bool,		gl_colorbfc);
extern_cfg (Bool,		gl_drawangles);

// =============================================================================
// -----------------------------------------------------------------------------
ForgeWindow::ForgeWindow()
{
	g_win = this;
	m_renderer = new GLRenderer;

	ui = new Ui_LDForgeUI;
	ui->setupUi (this);

	// Stuff the renderer into its frame
	QVBoxLayout* rendererLayout = new QVBoxLayout (ui->rendererFrame);
	rendererLayout->addWidget (R());

	connect (ui->objectList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_selectionChanged()));
	connect (ui->objectList, SIGNAL (itemDoubleClicked (QListWidgetItem*)), this, SLOT (slot_editObject (QListWidgetItem*)));
	connect (ui->fileList, SIGNAL (currentItemChanged (QListWidgetItem*, QListWidgetItem*)), this, SLOT (changeCurrentFile()));

	// Init message log manager
	m_msglog = new MessageManager;
	m_msglog->setRenderer (R());
	m_renderer->setMessageLog (m_msglog);
	m_quickColors = quickColorsFromConfig();
	slot_selectionChanged();
	setStatusBar (new QStatusBar);

	// Init primitive loader task stuff
	m_primLoaderBar = new QProgressBar;
	m_primLoaderWidget = new QWidget;
	QHBoxLayout* primLoaderLayout = new QHBoxLayout (m_primLoaderWidget);
	primLoaderLayout->addWidget (new QLabel ("Loading primitives:"));
	primLoaderLayout->addWidget (m_primLoaderBar);
	statusBar()->addPermanentWidget (m_primLoaderWidget);
	m_primLoaderWidget->hide();

	// Make certain actions checkable
	ui->actionAxes->setChecked (gl_axes);
	ui->actionWireframe->setChecked (gl_wireframe);
	ui->actionBFCView->setChecked (gl_colorbfc);
	updateGridToolBar();
	updateEditModeActions();
	updateRecentFilesMenu();
	updateToolBars();
	updateTitle();
	updateActionShortcuts();

	setMinimumSize (300, 200);

	connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup()));

	// Connect all actions
	for (QAction* act : findChildren<QAction*>())
		if (!act->objectName().isEmpty())
			connect (act, SIGNAL (triggered()), this, SLOT (slot_action()));
}

// =============================================================================
// -----------------------------------------------------------------------------
KeySequenceConfig* ForgeWindow::shortcutForAction (QAction* act)
{
	QString keycfgname = fmt ("key_%1", act->objectName());
	return KeySequenceConfig::getByName (keycfgname);
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::updateActionShortcuts()
{
	for (QAction* act : findChildren<QAction*>())
	{
		KeySequenceConfig* cfg = shortcutForAction (act);

		if (cfg)
			act->setShortcut (cfg->getValue());
	}
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::slot_action()
{
	// Get the name of the sender object and use it to compose the slot name.
	QString methodName = fmt ("slot_%1", sender()->objectName());

#ifdef DEBUG
	log ("Action %1 triggered", sender()->objectName());
#endif

	// Now invoke this slot to call the action.
	QMetaObject::invokeMethod (this, methodName.toAscii().constData(), Qt::DirectConnection);
	endAction();
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::endAction()
{
	// Add a step in the history now.
	getCurrentDocument()->addHistoryStep();

	// Update the list item of the current file - we may need to draw an icon
	// now that marks it as having unsaved changes.
	updateDocumentListItem (getCurrentDocument());
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::slot_lastSecondCleanup()
{
	delete m_renderer;
	delete ui;
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::updateRecentFilesMenu()
{
	// First, clear any items in the recent files menu
for (QAction * recent : m_recentFiles)
		delete recent;

	m_recentFiles.clear();

	QAction* first = null;

	for (const QVariant& it : io_recentfiles)
	{
		QString file = it.toString();
		QAction* recent = new QAction (getIcon ("open-recent"), file, this);

		connect (recent, SIGNAL (triggered()), this, SLOT (slot_recentFile()));
		ui->menuOpenRecent->insertAction (first, recent);
		m_recentFiles << recent;
		first = recent;
	}
}

// =============================================================================
// -----------------------------------------------------------------------------
QList<LDQuickColor> quickColorsFromConfig()
{
	QList<LDQuickColor> colors;

	for (QString colorname : gui_colortoolbar.split (":"))
	{
		if (colorname == "|")
			colors << LDQuickColor::getSeparator();
		else
		{
			LDColor* col = getColor (colorname.toLong());

			if (col != null)
				colors << LDQuickColor (col, null);
		}
	}

	return colors;
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::updateToolBars()
{
	m_colorButtons.clear();
	ui->colorToolbar->clear();

	for (LDQuickColor& entry : m_quickColors)
	{
		if (entry.isSeparator())
			ui->colorToolbar->addSeparator();
		else
		{
			QToolButton* colorButton = new QToolButton;
			colorButton->setIcon (makeColorIcon (entry.getColor(), 22));
			colorButton->setIconSize (QSize (22, 22));
			colorButton->setToolTip (entry.getColor()->name);

			connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor()));
			ui->colorToolbar->addWidget (colorButton);
			m_colorButtons << colorButton;

			entry.setToolButton (colorButton);
		}
	}

	updateGridToolBar();
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::updateGridToolBar()
{
	// Ensure that the current grid - and only the current grid - is selected.
	ui->actionGridCoarse->setChecked (grid == Grid::Coarse);
	ui->actionGridMedium->setChecked (grid == Grid::Medium);
	ui->actionGridFine->setChecked (grid == Grid::Fine);
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::updateTitle()
{
	QString title = fmt (APPNAME " %1", fullVersionString());

	// Append our current file if we have one
	if (getCurrentDocument())
	{
		if (getCurrentDocument()->getName().length() > 0)
			title += fmt (": %1", basename (getCurrentDocument()->getName()));
		else
			title += fmt (": <anonymous>");

		if (getCurrentDocument()->getObjectCount() > 0 &&
				getCurrentDocument()->getObject (0)->getType() == LDObject::EComment)
		{
			// Append title
			LDComment* comm = static_cast<LDComment*> (getCurrentDocument()->getObject (0));
			title += fmt (": %1", comm->text);
		}

		if (getCurrentDocument()->getHistory()->getPosition() != getCurrentDocument()->getSavePosition())
			title += '*';
	}

#ifdef DEBUG
	title += " [debug build]";
#elif BUILD_ID != BUILD_RELEASE
	title += " [pre-release build]";
#endif // DEBUG

	setWindowTitle (title);
}

// =============================================================================
// -----------------------------------------------------------------------------
int ForgeWindow::deleteSelection()
{
	if (selection().isEmpty())
		return 0;

	QList<LDObject*> selCopy = selection();

	// Delete the objects that were being selected
	for (LDObject* obj : selCopy)
		obj->deleteSelf();

	refresh();
	return selCopy.size();
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::buildObjList()
{
	if (!getCurrentDocument())
		return;

	// Lock the selection while we do this so that refreshing the object list
	// doesn't trigger selection updating so that the selection doesn't get lost
	// while this is done.
	g_isSelectionLocked = true;

	for (int i = 0; i < ui->objectList->count(); ++i)
		delete ui->objectList->item (i);

	ui->objectList->clear();

	for (LDObject* obj : getCurrentDocument()->getObjects())
	{
		QString descr;

		switch (obj->getType())
		{
			case LDObject::EComment:
			{
				descr = static_cast<LDComment*> (obj)->text;

				// Remove leading whitespace
				while (descr[0] == ' ')
					descr.remove (0, 1);
			} break;

			case LDObject::EEmpty:
				break; // leave it empty

			case LDObject::ELine:
			case LDObject::ETriangle:
			case LDObject::EQuad:
			case LDObject::ECondLine:
			{
				for (int i = 0; i < obj->vertices(); ++i)
				{
					if (i != 0)
						descr += ", ";

					descr += obj->getVertex (i).stringRep (true);
				}
			} break;

			case LDObject::EError:
			{
				descr = fmt ("ERROR: %1", obj->raw());
			} break;

			case LDObject::EVertex:
			{
				descr = static_cast<LDVertex*> (obj)->pos.stringRep (true);
			} break;

			case LDObject::ESubfile:
			{
				LDSubfile* ref = static_cast<LDSubfile*> (obj);

				descr = fmt ("%1 %2, (", ref->getFileInfo()->getDisplayName(), ref->getPosition().stringRep (true));

				for (int i = 0; i < 9; ++i)
					descr += fmt ("%1%2", ref->getTransform()[i], (i != 8) ? " " : "");

				descr += ')';
			} break;

			case LDObject::EBFC:
			{
				descr = LDBFC::statements[static_cast<LDBFC*> (obj)->type];
			} break;

			case LDObject::EOverlay:
			{
				LDOverlay* ovl = static_cast<LDOverlay*> (obj);
				descr = fmt ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->getCamera()],
					basename (ovl->getFileName()), ovl->getX(), ovl->getY(),
					ovl->getWidth(), ovl->getHeight());
			}
			break;

			default:
			{
				descr = obj->getTypeName();
			} break;
		}

		QListWidgetItem* item = new QListWidgetItem (descr);
		item->setIcon (getIcon (obj->getTypeName()));

		// Use italic font if hidden
		if (obj->isHidden())
		{
			QFont font = item->font();
			font.setItalic (true);
			item->setFont (font);
		}

		// Color gibberish orange on red so it stands out.
		if (obj->getType() == LDObject::EError)
		{
			item->setBackground (QColor ("#AA0000"));
			item->setForeground (QColor ("#FFAA00"));
		}
		elif (lv_colorize && obj->isColored() && obj->getColor() != maincolor && obj->getColor() != edgecolor)
		{
			// If the object isn't in the main or edge color, draw this
			// list entry in said color.
			LDColor* col = getColor (obj->getColor());

			if (col)
				item->setForeground (col->faceColor);
		}

		obj->qObjListEntry = item;
		ui->objectList->insertItem (ui->objectList->count(), item);
	}

	g_isSelectionLocked = false;
	updateSelection();
	scrollToSelection();
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::scrollToSelection()
{
	if (selection().isEmpty())
		return;

	LDObject* obj = selection().last();
	ui->objectList->scrollToItem (obj->qObjListEntry);
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::slot_selectionChanged()
{
	if (g_isSelectionLocked == true || getCurrentDocument() == null)
		return;

	// Update the shared selection array, though don't do this if this was
	// called during GL picking, in which case the GL renderer takes care
	// of the selection.
	if (m_renderer->isPicking())
		return;

	QList<LDObject*> priorSelection = selection();

	// Get the objects from the object list selection
	getCurrentDocument()->clearSelection();
	const QList<QListWidgetItem*> items = ui->objectList->selectedItems();

	for (LDObject* obj : getCurrentDocument()->getObjects())
	{
		for (QListWidgetItem* item : items)
		{
			if (item == obj->qObjListEntry)
			{
				obj->select();
				break;
			}
		}
	}

	// Update the GL renderer
	QList<LDObject*> compound = priorSelection + selection();
	removeDuplicates (compound);

	for (LDObject* obj : compound)
		m_renderer->compileObject (obj);

	m_renderer->update();
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::slot_recentFile()
{
	QAction* qAct = static_cast<QAction*> (sender());
	openMainFile (qAct->text());
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::slot_quickColor()
{
	QToolButton* button = static_cast<QToolButton*> (sender());
	LDColor* col = null;

	for (const LDQuickColor & entry : m_quickColors)
	{
		if (entry.getToolButton() == button)
		{
			col = entry.getColor();
			break;
		}
	}

	if (col == null)
		return;

	int newColor = col->index;

	for (LDObject* obj : selection())
	{
		if (obj->isColored() == false)
			continue; // uncolored object

		obj->setColor (newColor);
		R()->compileObject (obj);
	}

	endAction();
	refresh();
}

// =============================================================================
// -----------------------------------------------------------------------------
int ForgeWindow::getInsertionPoint()
{
	// If we have a selection, put the item after it.
	if (!selection().isEmpty())
		return selection().last()->getIndex() + 1;

	// Otherwise place the object at the end.
	return getCurrentDocument()->getObjectCount();
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::doFullRefresh()
{
	buildObjList();
	m_renderer->hardRefresh();
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::refresh()
{
	buildObjList();
	m_renderer->update();
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::updateSelection()
{
	g_isSelectionLocked = true;

	for (LDObject* obj : getCurrentDocument()->getObjects())
		obj->setSelected (false);

	ui->objectList->clearSelection();

	for (LDObject* obj : selection())
	{
		if (obj->qObjListEntry == null)
			continue;

		obj->qObjListEntry->setSelected (true);
		obj->setSelected (true);
	}

	g_isSelectionLocked = false;
	slot_selectionChanged();
}

// =============================================================================
// -----------------------------------------------------------------------------
int ForgeWindow::getSelectedColor()
{
	int result = -1;

	for (LDObject* obj : selection())
	{
		if (obj->isColored() == false)
			continue; // doesn't use color

		if (result != -1 && obj->getColor() != result)
			return -1; // No consensus in object color

		if (result == -1)
			result = obj->getColor();
	}

	return result;
}

// =============================================================================
// -----------------------------------------------------------------------------
LDObject::Type ForgeWindow::getUniformSelectedType()
{
	LDObject::Type result = LDObject::EUnidentified;

	for (LDObject* obj : selection())
	{
		if (result != LDObject::EUnidentified && obj->getColor() != result)
			return LDObject::EUnidentified;

		if (result == LDObject::EUnidentified)
			result = obj->getType();
	}

	return result;
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::closeEvent (QCloseEvent* ev)
{
	// Check whether it's safe to close all files.
	if (!safeToCloseAll())
	{
		ev->ignore();
		return;
	}

	// Save the configuration before leaving so that, for instance, grid choice
	// is preserved across instances.
	Config::save();

	ev->accept();
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::spawnContextMenu (const QPoint pos)
{
	const bool single = (selection().size() == 1);
	LDObject* singleObj = (single) ? selection()[0] : null;

	QMenu* contextMenu = new QMenu;

	if (single && singleObj->getType() != LDObject::EEmpty)
	{
		contextMenu->addAction (ui->actionEdit);
		contextMenu->addSeparator();
	}

	contextMenu->addAction (ui->actionCut);
	contextMenu->addAction (ui->actionCopy);
	contextMenu->addAction (ui->actionPaste);
	contextMenu->addAction (ui->actionDelete);
	contextMenu->addSeparator();
	contextMenu->addAction (ui->actionSetColor);

	if (single)
		contextMenu->addAction (ui->actionEditRaw);

	contextMenu->addAction (ui->actionBorders);
	contextMenu->addAction (ui->actionSetOverlay);
	contextMenu->addAction (ui->actionClearOverlay);
	contextMenu->addAction (ui->actionModeSelect);
	contextMenu->addAction (ui->actionModeDraw);
	contextMenu->addAction (ui->actionModeCircle);

	if (selection().size() > 0)
	{
		contextMenu->addSeparator();
		contextMenu->addAction (ui->actionSubfileSelection);
	}

	if (R()->camera() != GL::EFreeCamera)
	{
		contextMenu->addSeparator();
		contextMenu->addAction (ui->actionSetDrawDepth);
	}

	contextMenu->exec (pos);
}

// =============================================================================
// TODO: what the heh?
// -----------------------------------------------------------------------------
void ForgeWindow::deleteObjects (QList<LDObject*> objs)
{
	for (LDObject* obj : objs)
		obj->deleteSelf();
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::deleteByColor (const int colnum)
{
	QList<LDObject*> objs;

	for (LDObject* obj : getCurrentDocument()->getObjects())
	{
		if (!obj->isColored() || obj->getColor() != colnum)
			continue;

		objs << obj;
	}

	deleteObjects (objs);
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::updateEditModeActions()
{
	const EditMode mode = R()->getEditMode();
	ui->actionModeSelect->setChecked (mode == ESelectMode);
	ui->actionModeDraw->setChecked (mode == EDrawMode);
	ui->actionModeCircle->setChecked (mode == ECircleMode);
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::slot_editObject (QListWidgetItem* listitem)
{
	LDObject* obj = null;

	for (LDObject* it : getCurrentDocument()->getObjects())
	{
		if (it->qObjListEntry == listitem)
		{
			obj = it;
			break;
		}
	}

	AddObjectDialog::staticDialog (obj->getType(), obj);
}

#if 0
// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::primitiveLoaderStart (int max)
{
	m_primLoaderWidget->show();
	m_primLoaderBar->setRange (0, max);
	m_primLoaderBar->setValue (0);
	m_primLoaderBar->setFormat ("%p%");
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::primitiveLoaderUpdate (int prog)
{
	m_primLoaderBar->setValue (prog);
}

// =============================================================================
// -----------------------------------------------------------------------------
void ForgeWindow::primitiveLoaderEnd()
{
	QTimer* hidetimer = new QTimer;
	connect (hidetimer, SIGNAL (timeout()), m_primLoaderWidget, SLOT (hide()));
	hidetimer->setSingleShot (true);
	hidetimer->start (1500);
	m_primLoaderBar->setFormat (tr ("Done"));
	log (tr ("Primitives scanned: %1 primitives listed"), m_primLoaderBar->value());
}
#endif

// =============================================================================
// -----------------------------------------------------------------------------
bool ForgeWindow::save (LDDocument* f, bool saveAs)
{
	QString path = f->getFullPath();

	if (saveAs || path.isEmpty())
	{
		QString name = f->getDefaultName();

		if (!f->getFullPath().isEmpty()) 
			name = f->getFullPath();
		elif (!f->getName().isEmpty())
			name = f->getName();

		name.replace ("\\", "/");
		path = QFileDialog::getSaveFileName (g_win, tr ("Save As"),
			name, tr ("LDraw files (*.dat *.ldr)"));

		if (path.isEmpty())
		{
			// User didn't give a file name, abort.
			return false;
		}
	}

	if (f->save (path))
	{
		if (f == getCurrentDocument())
			updateTitle();

		log ("Saved to %1.", path);

		// Add it to recent files
		addRecentFile (path);
		return true;
	}

	QString message = fmt (tr ("Failed to save to %1: %2"), path, strerror (errno));

	// Tell the user the save failed, and give the option for saving as with it.
	QMessageBox dlg (QMessageBox::Critical, tr ("Save Failure"), message, QMessageBox::Close, g_win);

	// Add a save-as button
	QPushButton* saveAsBtn = new QPushButton (tr ("Save As"));
	saveAsBtn->setIcon (getIcon ("file-save-as"));
	dlg.addButton (saveAsBtn, QMessageBox::ActionRole);
	dlg.setDefaultButton (QMessageBox::Close);
	dlg.exec();

	if (dlg.clickedButton() == saveAsBtn)
		return save (f, true); // yay recursion!

	return false;
}

void ForgeWindow::addMessage (QString msg)
{
	m_msglog->addLine (msg);
}

// ============================================================================
void ObjectList::contextMenuEvent (QContextMenuEvent* ev)
{
	g_win->spawnContextMenu (ev->globalPos());
}

// =============================================================================
// -----------------------------------------------------------------------------
QPixmap getIcon (QString iconName)
{
	return (QPixmap (fmt (":/icons/%1.png", iconName)));
}

// =============================================================================
bool confirm (QString msg)
{
	return confirm (ForgeWindow::tr ("Confirm"), msg);
}

bool confirm (QString title, QString msg)
{
	return QMessageBox::question (g_win, title, msg,
		(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes;
}

// =============================================================================
void critical (QString msg)
{
	QMessageBox::critical (g_win, ForgeWindow::tr ("Error"), msg,
		(QMessageBox::Close), QMessageBox::Close);
}

// =============================================================================
QIcon makeColorIcon (LDColor* colinfo, const int size)
{
	// Create an image object and link a painter to it.
	QImage img (size, size, QImage::Format_ARGB32);
	QPainter paint (&img);
	QColor col = colinfo->faceColor;

	if (colinfo->index == maincolor)
	{
		// Use the user preferences for main color here
		col = gl_maincolor;
		col.setAlphaF (gl_maincolor_alpha);
	}

	// Paint the icon border
	paint.fillRect (QRect (0, 0, size, size), colinfo->edgeColor);

	// Paint the checkerboard background, visible with translucent icons
	paint.drawPixmap (QRect (1, 1, size - 2, size - 2), getIcon ("checkerboard"), QRect (0, 0, 8, 8));

	// Paint the color above the checkerboard
	paint.fillRect (QRect (1, 1, size - 2, size - 2), col);
	return QIcon (QPixmap::fromImage (img));
}

// =============================================================================
void makeColorComboBox (QComboBox* box)
{
	std::map<int, int> counts;

	for (LDObject* obj : getCurrentDocument()->getObjects())
	{
		if (!obj->isColored())
			continue;

		if (counts.find (obj->getColor()) == counts.end())
			counts[obj->getColor()] = 1;
		else
			counts[obj->getColor()]++;
	}

	box->clear();
	int row = 0;

	for (const auto& pair : counts)
	{
		LDColor* col = getColor (pair.first);
		assert (col != null);

		QIcon ico = makeColorIcon (col, 16);
		box->addItem (ico, fmt ("[%1] %2 (%3 object%4)",
			pair.first, col->name, pair.second, plural (pair.second)));
		box->setItemData (row, pair.first);

		++row;
	}
}

void ForgeWindow::updateDocumentList()
{
	ui->fileList->clear();

	for (LDDocument* f : g_loadedFiles)
	{
		// Don't list implicit files unless explicitly desired.
		if (f->isImplicit() && !gui_implicitfiles)
			continue;

		// Add an item to the list for this file and store a pointer to it in
		// the file, so we can find files by the list item.
		ui->fileList->addItem ("");
		QListWidgetItem* item = ui->fileList->item (ui->fileList->count() - 1);
		f->setListItem (item);
		updateDocumentListItem (f);
	}
}

void ForgeWindow::updateDocumentListItem (LDDocument* f)
{
	if (f->getListItem() == null)
	{
		// We don't have a list item for this file, so the list either doesn't
		// exist yet or is out of date. Build the list now.
		updateDocumentList();
		return;
	}

	// If this is the current file, it also needs to be the selected item on
	// the list.
	if (f == getCurrentDocument())
		ui->fileList->setCurrentItem (f->getListItem());

	// If we list implicit files, draw them with a shade of gray to make them
	// distinct.
	if (f->isImplicit())
		f->getListItem()->setForeground (QColor (96, 96, 96));

	f->getListItem()->setText (f->getDisplayName());

	// If the document has unsaved changes, draw a little icon next to it to mark that.
	f->getListItem()->setIcon (f->hasUnsavedChanges() ? getIcon ("file-save") : QIcon());
}

// =============================================================================
// A file is selected from the list of files on the left of the screen. Find out
// which file was picked and change to it.
void ForgeWindow::changeCurrentFile()
{
	LDDocument* f = null;
	QListWidgetItem* item = ui->fileList->currentItem();

	// Find the file pointer of the item that was selected.
	for (LDDocument* it : g_loadedFiles)
	{
		if (it->getListItem() == item)
		{
			f = it;
			break;
		}
	}

	// If we picked the same file we're currently on, we don't need to do
	// anything.
	if (!f || f == getCurrentDocument())
		return;

	LDDocument::setCurrent (f);
}

void ForgeWindow::refreshObjectList()
{
#if 0
	ui->objectList->clear();
	LDDocument* f = getCurrentDocument();

for (LDObject* obj : *f)
		ui->objectList->addItem (obj->qObjListEntry);

#endif

	buildObjList();
}

void ForgeWindow::updateActions()
{
	History* his = getCurrentDocument()->getHistory();
	int pos = his->getPosition();
	ui->actionUndo->setEnabled (pos != -1);
	ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1);
	ui->actionAxes->setChecked (gl_axes);
	ui->actionBFCView->setChecked (gl_colorbfc);
	ui->actionDrawAngles->setChecked (gl_drawangles);
}

QImage imageFromScreencap (uchar* data, int w, int h)
{
	// GL and Qt formats have R and B swapped. Also, GL flips Y - correct it as well.
	return QImage (data, w, h, QImage::Format_ARGB32).rgbSwapped().mirrored();
}

// =============================================================================
// -----------------------------------------------------------------------------
LDQuickColor::LDQuickColor (LDColor* color, QToolButton* toolButton) :
	m_Color (color),
	m_ToolButton (toolButton) {}

LDQuickColor LDQuickColor::getSeparator()
{
	return LDQuickColor (null, null);
}

bool LDQuickColor::isSeparator() const
{
	return getColor() == null;
}

mercurial