/*
* File: docwnd.cc
* Project: GED - bitmap editor (ICP)
* Author: name, login
* Team: xdudka00, xfilak01, xhefka00, xhradi08
* Created: YYYY-MM-DD
*/
#include <iostream>
#include <sstream>
#include <fstream>
#include <FL/Fl.H>
#include <FL/Fl_Menu_Bar.H>
#include <FL/Fl_Menu_Item.H>
#include <FL/Fl_File_Chooser.H>
#include "global.h"
#include "appwnd.h"
#include "menu.h"
#include "pluginsupport.h"
#include "gedconf.h"
#include "docwnd.h"
using std::string;
using GlobalH::minDocWndWidth;
using GlobalH::minDocWndHeight;
using GlobalH::defDocWndWidth;
using GlobalH::defDocWndHeight;
using GlobalH::menuBarHeight;
using GlobalH::min;
using GlobalH::max;
/*
* CallBacks
*/
static void cb_wndProc (Fl_Widget *pDocWnd, void *) {static_cast<DocWnd *>(pDocWnd)->tryClose (); }
/*
* Create document window
*/
DocWnd::DocWnd (Image *img)
: Fl_Window (defDocWndWidth-1, defDocWndHeight-1, (img->name()).c_str()), _img (img)
{
// Select whole image
_img->unSelect();
// Wnd properties
this->size_range (minDocWndWidth, minDocWndHeight);
this->callback (cb_wndProc);
this->begin();
// Create menu
_menu = new DocMenu (this, Point(0, 0), BoxSize(defDocWndWidth, menuBarHeight));
_menu -> menuInit (*_img);
// Create scroll area
_scroll = new MyFl_Scroll (0, menuBarHeight, defDocWndWidth, defDocWndHeight - 2*menuBarHeight);
_scroll->begin();
// Create paint area and load image
_paint = new PaintArea (Point (0, menuBarHeight), _img);
_scroll->end();
// Initial status bar message
std::ostringstream initStream;
initStream << "Image size: " << _img->size().width << "x" << _img->size().height;
_statusMsg = initStream.str();
// Create status bar
_statusBar = new StatusBar (
0, defDocWndHeight-menuBarHeight,
defDocWndWidth, menuBarHeight,
_statusMsg.c_str());
this->resizable (_scroll);
this->end ();
}
/*
* Unused destructor
*/
DocWnd::~DocWnd () {
}
/*
* Save image
* return: true if saved
*/
bool DocWnd::save () {
if (_img->bNew ())
return saveAs();
_img->save (_img->name());
_img->setSaved ();
setStatusMsg ("Image saved..");
this->redraw ();
return true;
}
/*
* Save as ...
* return: true if saved
*/
bool DocWnd::saveAs ()
{
char *szFileName;
for (;;) {
// Ask for name
szFileName = fl_file_chooser ("Save image", _img->filePattern().c_str(), 0);
if (NULL==szFileName) {
setStatusMsg ("Saving canceled by user..");
this->redraw ();
return false;
}
std::fstream f (szFileName, std::ios::in);
if (!f)
break;
f.close ();
std::string msg ("Overwrite exitsting file ");
msg.append (szFileName);
msg.append ("?");
if (1==fl_choice (msg.c_str(), "No", "Yes", 0))
break;
}
// Save with fileName
std::string fileName (szFileName);
_img -> save (fileName);
// Update some visible stuff
_img -> setSaved ();
this->setStatusMsg ("Image saved..");
this->label (szFileName);
this->redraw ();
return true;
}
/*
* Close window - return false if canceled
*/
bool DocWnd::tryClose () {
if (_img->needSave ()) {
// File changed
std::string msg ("Save changes in ");
msg.append (_img->name () + "?");
// Ask for save
switch (fl_choice (msg.c_str(), "Cancel", "Yes", "No")) {
case 0:
// Cancel
setStatusMsg ("Document closing canceled by user..");
this->redraw ();
return false;
case 1:
// Save
try {
if (!this->save ())
return false;
}
catch (Image::ErrSave ()) {
return false;
}
case 2:
default:
break;
}
}
// Destroy document window
this->hide ();
this->clear ();
Fl::delete_widget (this);
return true;
}
/*
* Undo
*/
void DocWnd::undo () {
_img->undo ();
setStatusMsg ("Last operation reverted..");
this->redraw();
}
/*
* Redo
*/
void DocWnd::redo () {
_img->redo ();
setStatusMsg ("Last undo reverted..");
this->redraw();
}
/*
* Select whole image
*/
void DocWnd::unSelect ()
{
*_img << new SelectCmd (_img);
this->setStatusMsg ("Selected all..");
this->redraw ();
}
/*
* Apply filter "grayscale"
*/
void DocWnd::filterGray ()
{
*_img << new GrayScaleCmd (_img);
setStatusMsg ("Built-in filter \'Grayscale\' called successfully..");
redraw ();
}
/*
* Apply filter "invert"
*/
void DocWnd::filterInvert ()
{
*_img << new InvertCmd (_img);
setStatusMsg ("Built-in filter \'Invert\' called successfully..");
redraw ();
}
/*
* Set status bar message text
*/
void DocWnd::setStatusMsg (const char *szMsg)
{
_statusMsg = szMsg;
_statusBar -> label (_statusMsg.c_str ());
}
/*
* Redraw image, init menu items and dispaly status message
*/
void DocWnd::redraw ()
{
_paint->resize (
- _scroll->xposition(), menuBarHeight - _scroll->yposition(),
_img->size().width, _img->size().height);
_paint->redraw ();
_menu->menuInit (*_img);
Fl_Window::redraw();
}
/*
* Draw status bar at low level
*/
void DocWnd::drawStatus ()
{
_statusBar -> draw();
}
/*
* Apply plugin "pluginName" with arguments "szArgs"
*/
void DocWnd::applyPlugin (std::string pluginName, const char *szArgs)
{
std::string args;
if (0!= szArgs)
args = szArgs;
std::ostringstream msgStream;
try {
*_img << new PluginCmd (_img, pluginName, args);
msgStream << "Plugin \'" << pluginName << " (" << args << ")\' called successfully..";
}
catch (PluginList::ErrPluginNotFound) {
msgStream << "Error: plugin \'" << pluginName << "\' not found!";
}
catch (PluginList::ErrPluginEC err) {
_img->rollBack ();
msgStream << "Error: plugin \'" << pluginName << "\' returned error code " << err.iErrCode;
}
setStatusMsg (msgStream.str ());
this->redraw ();
}
/*
* Start macro loading
*/
void DocWnd::macLoad ()
{
_img -> startMacro ();
_menu -> menuInit (*_img);
setStatusMsg ("Loading macro..");
this->redraw ();
}
/*
* Stop macro loading and create macro
*/
void DocWnd::macCreate (const char *szName)
{
const std::string macName (szName);
try {
GedConf *conf= GedConf::ptr ();
// Delete macro if redefined
MacroList macList= conf->getMacroList ();
if (macList.end() != macList.find (macName))
conf->removeMacro (macName);
// Save sequence as macro
MacroCmd *mac = _img->createMacroCmd ();
mac -> name (macName);
mac -> save ();
delete mac;
// Save configuration to disk
conf->saveConfig ();
// Update some visible stuff
string msg ("Added macro \'");
msg.append (macName);
msg.append ("\'");
setStatusMsg (msg);
this->redraw ();
}
catch (CmdHistory::ErrUnderflow) {
std::cerr << "GED2006: error: Macro underflow! (possible data lost)\n";
}
}
/*
* Apply macro "macName"
*/
void DocWnd::applyMacro (std::string macName)
{
MacroCmd *macro;
try {
// Load macro
macro = new MacroCmd (_img, macName);
}
catch (...) {
setStatusMsg ("Error occur during macro loading!");
redraw ();
return;
}
try {
// Execute macro
*_img << macro;
}
catch (...) {
// Roll-back
macro->unExec ();
_img->rollBack ();
setStatusMsg ("Error occur during macro execution!");
redraw ();
return;
}
// Update status bar
setStatusMsg ("Macro \'" + macName + "\'");
redraw ();
}
/*
* Status bar initialization
*/
StatusBar::StatusBar (int x, int y, int width, int height, const char *szText):
Fl_Box (FL_UP_BOX, x, y, width, height, szText)
{
align (FL_ALIGN_INSIDE | FL_ALIGN_LEFT);
}
/*
* Paint area initialization
*/
PaintArea::PaintArea (Point at, Image *img):
Fl_Box (at.x, at.y, img->size().width, img->size().height),
_img (img),
_dc (TOOL_NOTOOL, 0, 0),
_currentCmd (0)
{
_flImg = new MyFl_Image (*_img);
this->image (_flImg);
}
/*
* Paint area destructor
*/
PaintArea::~PaintArea ()
{
delete _flImg;
delete _img;
}
/*
* Draw paint area (need for selection)
*/
void PaintArea::draw ()
{
// Get scroller position
MyFl_Scroll *scroll = static_cast<MyFl_Scroll *>(parent ());
const int xPos = scroll->xposition ();
const int yPos = scroll->yposition ();
// Selected area metrics
Point at (_img->select().at);
at.x -= xPos;
at.y -= yPos;
at.y += menuBarHeight;
BoxSize size (_img->select().size);
// Draw selected area border
Fl_Box::draw ();
fl_line_style (FL_DOT, 1);
fl_draw_box (FL_BORDER_FRAME,
at.x, at.y,
size.width, size.height,
FL_BLACK);
fl_line_style (FL_SOLID, 1);
}
/*
* Redraw paint area
*/
void PaintArea::redraw ()
{
_flImg->uncache();
Fl_Box::redraw();
}
/*
* Paint area event handling
*/
int PaintArea::handle (int event)
{
// Event filter
switch (event) {
case FL_PUSH:
case FL_DRAG:
case FL_RELEASE:
if (1==Fl::event_button ())
break;
default:
return Fl_Widget::handle (event);
}
// Get pointers to parents
DocWnd *doc = static_cast<DocWnd *>(parent()->parent());
MyFl_Scroll *scroll = static_cast<MyFl_Scroll *>(parent ());
// Get scroller position
const int xPos = scroll->xposition ();
const int yPos = scroll->yposition ();
// Get mouse position
int x = Fl::event_x ();
int y = Fl::event_y ();
// Event handle
if (event == FL_PUSH) {
// Save context
_dc = AppWnd::ptr()->drawContext();
// Compute brush width
switch (_dc.tool) {
case TOOL_BRUSH:
case TOOL_LINE:
case TOOL_RECT:
_iBrushWidth = _dc.fgBrushWidth;
break;
case TOOL_RUBBER:
_iBrushWidth = _dc.bgBrushWidth;
break;
default:
_iBrushWidth = 1;
}
// Check fb selection
if (_dc.tool != TOOL_SELECT) {
_selX1 = _img->select().at.x;
_selY1 = _img->select().at.y;
_selX2 = _selX1 + _img->select().size.width;
_selY2 = _selY1 + _img->select().size.height;
} else {
_selX1 = 0;
_selY1 = 0;
_selX2 = _img->size().width;
_selY2 = _img->size().height;
}
_selX1 -= _iBrushWidth-1;
_selY1 -= _iBrushWidth-1;
_selX2 --;
_selY2 --;
// Absolute position
_absX1 = _absX2 = min (min (max (x, 0),
doc->w() - _iBrushWidth),
_img->size().width - xPos - _iBrushWidth);
_absY1 = _absY2 = min (min (max (y, int (menuBarHeight)),
doc->h() - _iBrushWidth - menuBarHeight+1),
_img->size().height - yPos - _iBrushWidth + menuBarHeight);
// Relative position
_relX1 = _relX2 = _absX1 + xPos;
_relY1 = _relY2 = _absY1 + yPos - menuBarHeight;
// Check position against selected area
_bInRange =
_relX2 >= _selX1 && _relX2 <= _selX2 &&
_relY2 >= _selY1 && _relY2 <= _relY2;
// Create draw command (if needed)
drawStart ();
drawData ();
// Start feedback
feedBackInit ();
feedBack ();
return 1;
} // (FL_PUSH)
if (_dc.tool == TOOL_CIRCLE) {
// Circle must be in aspect ratio 1:1
const int xMin = min (x, _absX1);
const int xMax = max (x, _absX1);
const int yMin = min (y, _absY1);
const int yMax = max (y, _absY1);
const int dx = xMax - xMin;
const int dy = yMax - yMin;
// Aspect ratio correction
if (dx > dy) {
if (x == xMin)
x = xMax - dy;
else
x = xMin + dy;
} else {
if (y == yMin)
y = yMax - dx;
else
y = yMin + dx;
}
}
// Absolute position
_absX2 = min (min (max (x, 0),
doc->w() - _iBrushWidth),
_img->size().width - xPos - _iBrushWidth);
_absY2 = min (min (max (y, int (menuBarHeight)),
doc->h() - _iBrushWidth - menuBarHeight+1),
_img->size().height - yPos - _iBrushWidth + menuBarHeight);
// Relative position
_relX2 = _absX2 + xPos;
_relY2 = _absY2 + yPos - menuBarHeight;
// Check position against selected area
_bInRange =
_relX2 >= _selX1 && _relX2 <= _selX2 &&
_relY2 >= _selY1 && _relY2 <= _relY2;
if (event == FL_DRAG) {
// FL_DRAG
drawData ();
feedBack ();
return 1;
}
// FL_RELEASE
doc -> setStatusMsg ("");
drawEnd ();
doc -> redraw();
return 1;
}
/*
* Called on mouse down event
*/
void PaintArea::drawStart ()
{
// Delete last command
delete _currentCmd;
// Create new command (if needed)
switch (_dc.tool) {
case TOOL_BRUSH:
_currentCmd = new BrushCmd (_img, _dc);
break;
case TOOL_RUBBER:
_currentCmd = new BrushCmd (_img, _dc, true);
break;
default:
_currentCmd = 0;
}
}
/*
* Called on mouse drag event
*/
void PaintArea::drawData ()
{
if (0== _currentCmd)
return;
switch (_dc.tool) {
case TOOL_BRUSH:
case TOOL_RUBBER:
if (_bInRange)
break;
default:
return;
}
// Add current point to pixmap
static_cast<BrushCmd *>(_currentCmd) -> add (Point (_relX2, _relY2));
}
/*
* Called on mouse release event
*/
void PaintArea::drawEnd ()
{
// Left-up corner (xMin,yMin) and right-bott corner (xMax, yMax)
const int xMin = min (_relX1, _relX2);
const int xMax = max (_relX1, _relX2);
const int yMin = min (_relY1, _relY2);
const int yMax = max (_relY1, _relY2);
// Drawn object placement
Point at;
BoxSize size;
if (_dc.tool == TOOL_LINE) {
at.x = _relX1;
at.y = _relY1;
size.width = _relX2 - _relX1;
size.height = _relY2 - _relY1;
} else {
at.x = xMin;
at.y = yMin;
size.width = xMax - xMin;
size.height = yMax - yMin;
}
const Rect placement (at, size);
switch (_dc.tool) {
case TOOL_BRUSH:
case TOOL_RUBBER:
if (0== _currentCmd)
return;
*_img << _currentCmd;
_currentCmd = 0;
break;
case TOOL_LINE:
*_img << new LineCmd (_img, placement, _dc);
break;
case TOOL_RECT:
*_img << new RectCmd (_img, placement, _dc);
break;
case TOOL_CIRCLE:
*_img << new CircleCmd (_img, placement, _dc);
break;
case TOOL_ELLIPSE:
*_img << new EllipseCmd (_img, placement, _dc);
break;
case TOOL_TEXT:
{
const char *szText= fl_input ("Draw text:");
if (0== szText || '\0'== *szText)
return;
try {
*_img << new TextCmd (_img, _dc, Point (_relX2, _relY2), szText);
}
catch (TextCmd::ErrFreeType) {
_img -> rollBack ();
DocWnd *const doc = static_cast<DocWnd *>(parent()->parent());
doc->setStatusMsg ("Error occur during text draw!");
}
break;
}
case TOOL_SELECT:
*_img << new SelectCmd (_img, placement);
break;
default:
return;
}
}
/*
* Prepare document window for feedback during draw
*/
void PaintArea::feedBackInit ()
{
// Save current context
Fl_Window *last = Fl_Window::current();
DocWnd *doc = static_cast<DocWnd *>(parent()->parent());
// Redraw paint area, hide scrolling
doc -> make_current ();
fl_push_clip (
0, menuBarHeight,
min (doc->w(), _img->size().width),
min (doc->h()-2*menuBarHeight+1, _img->size().height));
this->draw ();
fl_pop_clip ();
// Restore context
if (
last== AppWnd::ptr() ||
AppWnd::ptr()->isDocument (last))
last -> make_current();
}
/*
* Feedback during draw
*/
void PaintArea::feedBack ()
{
// Save context
Fl_Window *last = Fl_Window::current();
DocWnd *doc = static_cast<DocWnd *>(parent()->parent());
// Left-up corner (xMin,yMin) and right-bott corner (xMax, yMax)
const int xMin = min (_absX1, _absX2);
const int xMax = max (_absX1, _absX2);
const int yMin = min (_absY1, _absY2);
const int yMax = max (_absY1, _absY2);
const int dx = xMax - xMin;
const int dy = yMax - yMin;
// Draw feedback
std::ostringstream msg;
doc->make_current ();
switch (_dc.tool) {
case TOOL_NOTOOL:
// no feedback
msg << "Scrollbars hidden";
break;
case TOOL_TEXT:
msg << "Text placement";
break;
case TOOL_BRUSH:
msg << "Brush";
fl_color (_dc.fgColor.red, _dc.fgColor.green, _dc.fgColor.blue);
fl_line_style (FL_SOLID, _iBrushWidth);
fl_line (
_absX2 + _iBrushWidth/2, _absY2,
_absX2 + _iBrushWidth/2, _absY2 + _iBrushWidth);
break;
case TOOL_RUBBER:
msg << "Rubber";
fl_color (_dc.bgColor.red, _dc.bgColor.green, _dc.bgColor.blue);
fl_line_style (FL_SOLID, _iBrushWidth);
fl_line (
_absX2 + _iBrushWidth/2, _absY2,
_absX2 + _iBrushWidth/2, _absY2 + _iBrushWidth);
break;
case TOOL_LINE:
msg << "Drawing line";
feedBackInit ();
fl_color (_dc.fgColor.red, _dc.fgColor.green, _dc.fgColor.blue);
fl_line_style (FL_SOLID, _dc.fgBrushWidth);
fl_line (
_absX1 + _iBrushWidth/2, _absY1 + _iBrushWidth/2,
_absX2 + _iBrushWidth/2, _absY2 + _iBrushWidth/2);
break;
case TOOL_RECT:
msg << "Drawing rectangle";
feedBackInit ();
fl_line_style (FL_SOLID, _dc.fgBrushWidth);
fl_draw_box (FL_BORDER_FRAME, xMin + _iBrushWidth/2, yMin + _iBrushWidth/2, dx, dy,
fl_rgb_color (_dc.fgColor.red, _dc.fgColor.green, _dc.fgColor.blue));
break;
case TOOL_CIRCLE:
case TOOL_ELLIPSE:
case TOOL_SELECT:
msg << "Select area";
feedBackInit ();
fl_line_style (FL_DOT,1);
fl_draw_box (FL_BORDER_FRAME, xMin, yMin, dx, dy,
fl_rgb_color (_dc.fgColor.red, _dc.fgColor.green, _dc.fgColor.blue));
break;
}
fl_color (FL_BLACK);
fl_line_style (FL_SOLID,1);
msg << ", effective mouse position: (" << (_relX2) << ", " << (_relY2) << ")";
doc->setStatusMsg (msg.str ());
doc->drawStatus ();
// Restore context
if (
last== AppWnd::ptr() ||
AppWnd::ptr()->isDocument (last))
last -> make_current();
}