rawTherapee/rtgui/guiutils.h
Hombre c7be386c3a Solving issue #3531, assign classes to Gtk::Window and MyWidget
- Curve editor buttons are set to expand by default, but they are set to
shrink as soon as an accompagnying widget is set to expand
- White Balance's method button now has a centered ellipse ("...")
- White Balance's buttons are now aligned on their right
- A "withScrollbar" class is added to MyExpander if the ToolPanel's
vertical scrollbar is visible. This can let you add padding space for
the scrollbar (see #MyExpander.withScrollbar in RT default theme)
- A "maximized" and "fullscreen" class is added to the RTWindow whenever
it change state ; BEWARE: if you maximize the window then make it
fullscreen, Gtk says that the window is in a "maximized & fullscreen"
state, which mean that both class can be added at the same time to the
window.

One Gtk oddity (at least on Windows) is that you can make your window
fullscreen and still drag it around by its header bar... That's not very
practical to click on the unfullscreen button if in Single Editor mode
with vertical Tab.

I also managed to see the window in a Inconified + Maximized state. This
part of Gtk doesn't seem very robust, on Windows at least.
2016-12-20 01:58:37 +01:00

569 lines
18 KiB
C++

/*
* This file is part of RawTherapee.
*
* Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
*
* RawTherapee 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.
*
* RawTherapee 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 RawTherapee. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __GUI_UTILS_
#define __GUI_UTILS_
#include <gtkmm.h>
#include <cairomm/cairomm.h>
#include "../rtengine/rtengine.h"
#include "../rtengine/coord.h"
#include "rtimage.h"
#include <sstream>
#include <iostream>
Glib::ustring escapeHtmlChars(const Glib::ustring &src);
bool removeIfThere (Gtk::Container* cont, Gtk::Widget* w, bool increference = true);
void thumbInterp (const unsigned char* src, int sw, int sh, unsigned char* dst, int dw, int dh);
Glib::ustring removeExtension (const Glib::ustring& filename);
Glib::ustring getExtension (const Glib::ustring& filename);
bool confirmOverwrite (Gtk::Window& parent, const std::string& filename);
void writeFailed (Gtk::Window& parent, const std::string& filename);
void drawCrop (Cairo::RefPtr<Cairo::Context> cr, int imx, int imy, int imw, int imh, int startx, int starty, double scale, const rtengine::procparams::CropParams& cparams, bool drawGuide = true, bool useBgColor = true, bool fullImageVisible = true);
gboolean acquireGUI(void* data);
void setExpandAlignProperties(Gtk::Widget *widget, bool hExpand, bool vExpand, enum Gtk::Align hAlign, enum Gtk::Align vAlign);
guint add_idle (GSourceFunc function, gpointer data);
// TODO: The documentation says gdk_threads_enter and gdk_threads_leave should be replaced
// by g_main_context_invoke(), g_idle_add() and related functions, but this will require more extensive changes.
// We silence those warnings until then so that we notice the others.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
/**
* @brief Lock GTK for critical section.
*
* Will unlock on destruction. To use:
*
* <code>
* {
* GThreadLock lock;
* // critical code
* }
* </code>
*/
class GThreadLock
{
public:
GThreadLock()
{
gdk_threads_enter();
}
~GThreadLock()
{
gdk_threads_leave();
}
};
/**
* @brief Unlock GTK critical section.
*
* Will relock on destruction.
*/
class GThreadUnLock
{
public:
GThreadUnLock()
{
gdk_threads_leave();
}
~GThreadUnLock()
{
gdk_threads_enter();
}
};
#pragma GCC diagnostic pop
class ConnectionBlocker
{
public:
explicit ConnectionBlocker (Gtk::Widget *associatedWidget, sigc::connection& connection) : connection (associatedWidget ? &connection : nullptr)
{
if (this->connection) {
wasBlocked = connection.block();
}
}
explicit ConnectionBlocker (sigc::connection& connection) : connection (&connection)
{
wasBlocked = connection.block();
}
~ConnectionBlocker ()
{
if (connection) {
connection->block(wasBlocked);
}
}
private:
sigc::connection *connection;
bool wasBlocked;
};
/**
* @brief Glue box to control visibility of the MyExpender's content ; also handle the frame around it
*/
class ExpanderBox: public Gtk::EventBox
{
private:
Gtk::Container *pC;
public:
explicit ExpanderBox( Gtk::Container *p);
~ExpanderBox( )
{
delete pC;
}
void setLevel(int level);
void show() {}
void show_all();
void hide() {}
void set_visible(bool isVisible = true) {}
void showBox();
void hideBox();
// bool on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr);
};
/**
* @brief A custom Expander class, that can handle widgets in the title bar
*
* Custom made expander for responsive widgets in the header. It also handle a "enabled/disabled" property that display
* a different arrow depending on this boolean value.
*
* Warning: once you've instantiated this class with a text label or a widget label, you won't be able to revert to the other solution.
*/
class MyExpander : public Gtk::VBox
{
public:
typedef sigc::signal<void> type_signal_enabled_toggled;
private:
type_signal_enabled_toggled message;
static Glib::RefPtr<Gdk::Pixbuf> inconsistentPBuf; /// "inconsistent" image, displayed when useEnabled is true ; in this case, nothing will tell that an expander is opened/closed
static Glib::RefPtr<Gdk::Pixbuf> enabledPBuf; /// "enabled" image, displayed when useEnabled is true ; in this case, nothing will tell that an expander is opened/closed
static Glib::RefPtr<Gdk::Pixbuf> disabledPBuf; /// "disabled" image, displayed when useEnabled is true ; in this case, nothing will tell that an expander is opened/closed
static Glib::RefPtr<Gdk::Pixbuf> openedPBuf; /// "opened" image, displayed when useEnabled is false
static Glib::RefPtr<Gdk::Pixbuf> closedPBuf; /// "closed" image, displayed when useEnabled is false
bool enabled; /// Enabled feature (default to true)
bool inconsistent; /// True if the enabled button is inconsistent
Gtk::EventBox *titleEvBox; /// EventBox of the title, to get a connector from it
Gtk::HBox *headerHBox;
bool flushEvent; /// Flag to control the weird event mechanism of Gtk (please prove me wrong!)
ExpanderBox* expBox; /// Frame that includes the child and control its visibility
Gtk::EventBox *imageEvBox; /// Enable/Disable or Open/Close arrow event box
/// Triggered on opened/closed event
bool on_toggle(GdkEventButton* event);
/// Triggered on enabled/disabled change -> will emit a toggle event to the connected objects
bool on_enabled_change(GdkEventButton* event);
/// Used to handle the colored background for the whole Title
bool on_enter_leave_title (GdkEventCrossing* event);
/// Used to handle the colored background for the Enable button
bool on_enter_leave_enable (GdkEventCrossing* event);
void updateStyle();
protected:
Gtk::Container* child; /// Gtk::Contained to display below the expander's title
Gtk::Widget* headerWidget; /// Widget to display in the header, next to the arrow image ; can be NULL if the "string" version of the ctor has been used
Gtk::Image* statusImage; /// Image to display the opened/closed status (if useEnabled is false) of the enabled/disabled status (if useEnabled is true)
Gtk::Label* label; /// Text to display in the header, next to the arrow image ; can be NULL if the "widget" version of the ctor has been used
bool useEnabled; /// Set whether to handle an enabled/disabled feature and display the appropriate images
public:
/** @brief Create a custom expander with a simple header made of a label.
* @param useEnabled Set whether to handle an enabled/disabled toggle button and display the appropriate image
* @param titleLabel A string to display in the header. Warning: you won't be able to switch to a widget label.
*/
MyExpander(bool useEnabled, Glib::ustring titleLabel);
/** Create a custom expander with a a custom - and responsive - widget
* @param useEnabled Set whether to handle an enabled/disabled toggle button and display the appropriate image
* @param titleWidget A widget to display in the header. Warning: you won't be able to switch to a string label.
*/
MyExpander(bool useEnabled, Gtk::Widget* titleWidget);
/// Initialize the class by loading the images
static void init();
Glib::SignalProxy1< bool, GdkEventButton* > signal_button_release_event()
{
return titleEvBox->signal_button_release_event();
};
type_signal_enabled_toggled signal_enabled_toggled();
/// Set the nesting level of the Expander to adapt its style accordingly
void setLevel(int level);
/// Set a new label string. If it has been instantiated with a Gtk::Widget, this method will do nothing
void setLabel (Glib::ustring newLabel);
/// Set a new label string. If it has been instantiated with a Gtk::Widget, this method will do nothing
void setLabel (Gtk::Widget *newWidget);
/// Get whether the enabled option is set (to true or false) or unset (i.e. undefined)
bool get_inconsistent();
/// Set whether the enabled option is set (to true or false) or unset (i.e. undefined)
void set_inconsistent(bool isInconsistent);
/// Get whether the enabled button is used or not
bool getUseEnabled();
/// Get whether the enabled button is on or off
bool getEnabled();
/// If not inconsistent, set the enabled button to true or false and emit the message if the state is different
/// If inconsistent, set the internal value to true or false, but do not update the image and do not emit the message
void setEnabled(bool isEnabled);
/// Adds a Tooltip to the Enabled button, if it exist ; do nothing otherwise
void setEnabledTooltipMarkup(Glib::ustring tooltipMarkup);
void setEnabledTooltipText(Glib::ustring tooltipText);
/// Get the header widget. It'll send back the Gtk::Label* if it has been instantiated with a simple text
Gtk::Widget* getLabelWidget() const
{
return headerWidget ? headerWidget : label;
}
/// Get the widget shown/hidden by the expander
Gtk::Container* getChild();
/// Set the collapsed/expanded state of the expander
void set_expanded( bool expanded );
/// Get the collapsed/expanded state of the expander
bool get_expanded();
/// Add a Gtk::Container for the content of the expander
/// Warning: do not manually Show/Hide the widget, because this parameter is handled by the click on the Expander's title
void add (Gtk::Container& widget);
void updateVScrollbars(bool hide);
};
/**
* @brief subclass of Gtk::ScrolledWindow in order to handle the scrollwheel
*/
class MyScrolledWindow : public Gtk::ScrolledWindow
{
bool on_scroll_event (GdkEventScroll* event);
void get_preferred_height_vfunc (int& minimum_height, int& natural_height) const;
void get_preferred_height_for_width_vfunc (int width, int &minimum_height, int &natural_height) const;
public:
MyScrolledWindow();
};
/**
* @brief subclass of Gtk::ComboBox in order to handle the scrollwheel
*/
class MyComboBox : public Gtk::ComboBox
{
int naturalWidth, minimumWidth;
bool on_scroll_event (GdkEventScroll* event);
void get_preferred_width_vfunc (int &minimum_width, int &natural_width) const;
void get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const;
public:
MyComboBox ();
void setPreferredWidth (int minimum_width, int natural_width);
};
/**
* @brief subclass of Gtk::ComboBoxText in order to handle the scrollwheel
*/
class MyComboBoxText : public Gtk::ComboBoxText
{
int naturalWidth, minimumWidth;
bool on_scroll_event (GdkEventScroll* event);
void get_preferred_width_vfunc (int &minimum_width, int &natural_width) const;
void get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const;
public:
MyComboBoxText (bool has_entry = false);
void setPreferredWidth (int minimum_width, int natural_width);
};
/**
* @brief subclass of Gtk::SpinButton in order to handle the scrollwheel
*/
class MySpinButton : public Gtk::SpinButton
{
protected:
bool on_scroll_event (GdkEventScroll* event);
bool on_key_press_event (GdkEventKey* event);
public:
MySpinButton ();
void updateSize();
};
/**
* @brief subclass of Gtk::HScale in order to handle the scrollwheel
*/
class MyHScale : public Gtk::HScale
{
bool on_scroll_event (GdkEventScroll* event);
bool on_key_press_event (GdkEventKey* event);
};
/**
* @brief subclass of Gtk::FileChooserButton in order to handle the scrollwheel
*/
class MyFileChooserButton : public Gtk::FileChooserButton
{
protected:
bool on_scroll_event (GdkEventScroll* event);
void get_preferred_width_vfunc (int &minimum_width, int &natural_width) const;
void get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const;
public:
MyFileChooserButton (const Glib::ustring& title, Gtk::FileChooserAction action = Gtk::FILE_CHOOSER_ACTION_OPEN);
};
/**
* @brief A helper method to connect the current folder property of a file chooser to an arbitrary variable.
*/
void bindCurrentFolder (Gtk::FileChooser& chooser, Glib::ustring& variable);
typedef enum RTUpdatePolicy {
RTUP_STATIC,
RTUP_DYNAMIC
} eUpdatePolicy;
typedef enum RTOrientation {
RTO_Left2Right,
RTO_Bottom2Top,
RTO_Right2Left,
RTO_Top2Bottom
} eRTOrientation;
enum TOITypes {
TOI_TEXT,
TOI_ICON
};
typedef enum RTNav {
NAV_NONE,
NAV_NEXT,
NAV_PREVIOUS
} eRTNav;
/**
* @brief Handle the switch between text and image to be displayed in the HBox (to be used in a button/toolpanel)
*/
class TextOrIcon : public Gtk::HBox
{
protected:
Gtk::Image* imgIcon;
Gtk::Label* label;
Glib::ustring filename;
Glib::ustring labelText;
Glib::ustring tooltipText;
public:
TextOrIcon (Glib::ustring filename, Glib::ustring labelTx, Glib::ustring tooltipTx, TOITypes type);
~TextOrIcon ();
void switchTo(TOITypes type);
};
class MyImageMenuItem : public Gtk::MenuItem
{
private:
Gtk::Grid *box;
RTImage *image;
Gtk::Label *label;
public:
MyImageMenuItem (Glib::ustring label, Glib::ustring imageFileName);
const RTImage *getImage () const;
const Gtk::Label* getLabel () const;
};
class MyProgressBar : public Gtk::ProgressBar
{
private:
int w;
void get_preferred_width_vfunc (int &minimum_width, int &natural_width) const;
void get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const;
public:
MyProgressBar(int width);
MyProgressBar();
void setPreferredWidth(int width);
};
/**
* @brief Define a gradient milestone
*/
class GradientMilestone
{
public:
double position;
double r;
double g;
double b;
double a;
GradientMilestone(double _p = 0., double _r = 0., double _g = 0., double _b = 0., double _a = 0.)
{
position = _p;
r = _r;
g = _g;
b = _b;
a = _a;
}
};
class RefCount
{
private:
int refCount;
public:
RefCount() : refCount(1) {}
virtual ~RefCount() {}
void reference()
{
++refCount;
}
void unreference()
{
--refCount;
if (!refCount) {
delete this;
}
}
};
/**
* @brief Handle back buffers as automatically as possible, and suitable to be used with Glib::RefPtr
*/
class BackBuffer : public RefCount
{
protected:
int x, y, w, h; // Rectangle where the colored bar has to be drawn
rtengine::Coord offset; // Offset of the source region to draw, relative to the top left corner
Cairo::RefPtr<Cairo::ImageSurface> surface;
bool dirty; // mean that the Surface has to be (re)allocated
public:
BackBuffer();
BackBuffer(int w, int h, Cairo::Format format = Cairo::FORMAT_RGB24);
BackBuffer(int w, int h, Glib::RefPtr<Gdk::Window> referenceWindow);
// set the destination drawing rectangle; return true if the dimensions are different
// Note: newW & newH must be > 0
bool setDrawRectangle(Glib::RefPtr<Gdk::Window> window, Gdk::Rectangle &rectangle, bool updateBackBufferSize = true);
bool setDrawRectangle(Glib::RefPtr<Gdk::Window> window, int newX, int newY, int newW, int newH, bool updateBackBufferSize = true);
bool setDrawRectangle(Cairo::Format format, Gdk::Rectangle &rectangle, bool updateBackBufferSize = true);
bool setDrawRectangle(Cairo::Format format, int newX, int newY, int newW, int newH, bool updateBackBufferSize = true);
// set the destination drawing location, do not modify other parameters like size and offset. Use setDrawRectangle to set all parameters at the same time
void setDestPosition(int x, int y);
void setSrcOffset(int x, int y);
void setSrcOffset(const rtengine::Coord &newOffset);
void getSrcOffset(int &x, int &y);
void getSrcOffset(rtengine::Coord &offset);
void copyRGBCharData(const unsigned char *srcData, int srcX, int srcY, int srcW, int srcH, int srcRowStride, int dstX, int dstY);
void copySurface(Glib::RefPtr<Gdk::Window> window, Gdk::Rectangle *rectangle = nullptr);
void copySurface(BackBuffer *destBackBuffer, Gdk::Rectangle *rectangle = nullptr);
void copySurface(Cairo::RefPtr<Cairo::ImageSurface> destSurface, Gdk::Rectangle *rectangle = nullptr);
void copySurface(Cairo::RefPtr<Cairo::Context> crDest, Gdk::Rectangle *destRectangle = nullptr);
void setDirty(bool isDirty)
{
dirty = isDirty;
if (!dirty && !surface) {
dirty = true;
}
}
bool isDirty()
{
return dirty;
}
// you have to check if the surface is created thanks to surfaceCreated before starting to draw on it
bool surfaceCreated()
{
return static_cast<bool>(surface);
}
Cairo::RefPtr<Cairo::ImageSurface> getSurface()
{
return surface;
}
void setSurface(Cairo::RefPtr<Cairo::ImageSurface> surf)
{
surface = surf;
}
void deleteSurface()
{
if (surface) {
surface.clear();
}
dirty = true;
}
// will let you get a Cairo::Context for Cairo drawing operations
Cairo::RefPtr<Cairo::Context> getContext()
{
return Cairo::Context::create(surface);
}
int getWidth()
{
return surface ? surface->get_width() : 0; // sending back the allocated width
}
int getHeight()
{
return surface ? surface->get_height() : 0; // sending back the allocated height
}
};
inline void setActiveTextOrIndex (Gtk::ComboBoxText& comboBox, const Glib::ustring& text, int index)
{
comboBox.set_active_text (text);
if (comboBox.get_active_row_number () < 0) {
comboBox.set_active (index);
}
}
inline Gtk::Window& getToplevelWindow (Gtk::Widget* widget)
{
return *static_cast<Gtk::Window*> (widget->get_toplevel ());
}
#endif