1 /*
2 *  This file is part of RawTherapee.
3 *
4 *  RawTherapee is free software: you can redistribute it and/or modify
5 *  it under the terms of the GNU General Public License as published by
6 *  the Free Software Foundation, either version 3 of the License, or
7 *  (at your option) any later version.
8 *
9 *  RawTherapee is distributed in the hope that it will be useful,
10 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 *  GNU General Public License for more details.
13 *
14 *  You should have received a copy of the GNU General Public License
15 *  along with RawTherapee.  If not, see <http://www.gnu.org/licenses/>.
16 */
17 
18 #include "editwindow.h"
19 #include "version.h"
20 #include "options.h"
21 #include "preferences.h"
22 #include "cursormanager.h"
23 #include "rtwindow.h"
24 #include <gtk/gtk.h>
25 #include "rtimage.h"
26 #include "threadutils.h"
27 
28 extern Glib::ustring argv0;
29 
30 // Check if the system has more than one display and option is set
isMultiDisplayEnabled()31 bool EditWindow::isMultiDisplayEnabled()
32 {
33     return options.multiDisplayMode > 0 && Gdk::Screen::get_default()->get_n_monitors() > 1;
34 }
35 
36 // Should only be created once, auto-creates window on correct display
getInstance(RTWindow * p,bool restore)37 EditWindow* EditWindow::getInstance(RTWindow* p, bool restore)
38 {
39     struct EditWindowInstance
40     {
41         EditWindow editWnd;
42 
43         explicit EditWindowInstance(RTWindow* p) : editWnd(p)
44         {
45         }
46     };
47 
48     static EditWindowInstance instance_(p);
49     if(restore) {
50         instance_.editWnd.restoreWindow();
51     }
52     return &instance_.editWnd;
53 }
54 
EditWindow(RTWindow * p)55 EditWindow::EditWindow (RTWindow* p) : resolution(RTScalable::baseDPI), parent(p) , isFullscreen(false), isClosed(true)
56 {
57 
58     updateResolution();
59     setAppIcon();
60     set_title_decorated("");
61     set_modal(false);
62     set_resizable(true);
63     set_default_size(options.meowWidth, options.meowHeight);
64 
65     property_destroy_with_parent().set_value(false);
66 
67     mainNB = Gtk::manage(new Gtk::Notebook ());
68     mainNB->set_scrollable(true);
69     mainNB->signal_switch_page().connect_notify(sigc::mem_fun(*this, &EditWindow::on_mainNB_switch_page));
70 
71     signal_key_press_event().connect(sigc::mem_fun(*this, &EditWindow::keyPressed));
72 
73     Gtk::VBox* mainBox = Gtk::manage(new Gtk::VBox());
74     mainBox->pack_start(*mainNB);
75 
76     add(*mainBox);
77 
78 }
79 
restoreWindow()80 void EditWindow::restoreWindow() {
81 
82     if(isClosed) {
83         int meowMonitor = 0;
84         if(isMultiDisplayEnabled()) {
85             if(options.meowMonitor >= 0) { // use display from last session if available
86                 meowMonitor = std::min(options.meowMonitor, Gdk::Screen::get_default()->get_n_monitors() - 1);
87             } else { // Determine the other display
88                 const Glib::RefPtr< Gdk::Window >& wnd = parent->get_window();
89                 meowMonitor = parent->get_screen()->get_monitor_at_window(wnd) == 0 ? 1 : 0;
90             }
91         }
92 
93         Gdk::Rectangle lMonitorRect;
94         get_screen()->get_monitor_geometry(meowMonitor, lMonitorRect);
95         if(options.meowMaximized) {
96             move(lMonitorRect.get_x(), lMonitorRect.get_y());
97             maximize();
98         } else {
99             resize(options.meowWidth, options.meowHeight);
100             if(options.meowX <= lMonitorRect.get_x() + lMonitorRect.get_width() && options.meowY <= lMonitorRect.get_y() + lMonitorRect.get_height()) {
101                 move(options.meowX, options.meowY);
102             } else {
103                 move(lMonitorRect.get_x(), lMonitorRect.get_y());
104             }
105         }
106         show_all();
107 
108         isFullscreen = options.meowFullScreen;
109 
110         if(isFullscreen) {
111             fullscreen();
112         }
113 
114         isClosed = false;
115     }
116 
117 }
118 
on_realize()119 void EditWindow::on_realize ()
120 {
121     Gtk::Window::on_realize ();
122 
123     editWindowCursorManager.init (get_window());
124 }
125 
updateResolution()126 bool EditWindow::updateResolution()
127 {
128     int scale = get_scale_factor();
129     double res = get_screen()->get_resolution();
130     if (scale == 2) {
131         // from Windows' behavior : if scale==2, resolution = 192. (Gtk shows 96 dpi !?), there's no higher value
132         res = RTScalable::baseHiDPI;
133     }
134     bool retVal = res != resolution;
135     resolution = res;
136     return retVal;
137 }
138 
setAppIcon()139 void EditWindow::setAppIcon()
140 {
141     Glib::ustring fName;
142     bool downsize = false;
143     // findIconAbsolutePath won't be able to select the image based on resolution with the
144     // storage of the images, we're doing the selection here
145     if (resolution == RTScalable::baseDPI) {
146         fName = "ART-logo-24.png";
147     } else {
148         fName = "ART-logo-48.png";
149         if (resolution < RTScalable::baseHiDPI) {
150             downsize = true;
151         }
152     }
153     Glib::ustring icon_path = Glib::build_filename (argv0, "images", fName);
154     const Glib::RefPtr<Gdk::Pixbuf> pixbuf = Gdk::Pixbuf::create_from_file(icon_path);
155     if (!pixbuf) {
156         return;
157     }
158     if (downsize) {
159         int size = int((48. * resolution) / RTScalable::baseHiDPI);
160         pixbuf->scale_simple(size, size, Gdk::InterpType::INTERP_BILINEAR);
161     }
162 
163     try {
164         set_default_icon(pixbuf);
165     } catch(Glib::Exception& ex) {
166         printf ("%s\n", ex.what().c_str());
167     }
168 }
169 
on_configure_event(GdkEventConfigure * event)170 bool EditWindow::on_configure_event(GdkEventConfigure* event)
171 {
172     if (updateResolution()) {
173         setAppIcon();
174     }
175 
176     if (get_realized() && is_visible()) {
177         if(!is_maximized()) {
178             get_position(options.meowX, options.meowY);
179             get_size(options.meowWidth, options.meowHeight);
180         }
181         options.meowMaximized = is_maximized();
182     }
183 
184     return Gtk::Widget::on_configure_event(event);
185 }
186 
187 /*  HOMBRE: Disabling this since it's maximized when opened anyway.
188  *  Someday, the EditorWindow might save its own position and state, so it'll have to be uncommented
189 bool EditWindow::on_window_state_event(GdkEventWindowState* event)
190 {
191     if (event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) {
192         options.windowMaximized = event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED;
193     }
194 
195     return Gtk::Widget::on_window_state_event(event);
196 }*/
197 
on_mainNB_switch_page(Gtk::Widget * widget,guint page_num)198 void EditWindow::on_mainNB_switch_page(Gtk::Widget* widget, guint page_num)
199 {
200     //if (page_num > 1) {
201     EditorPanel *ep = static_cast<EditorPanel*>(widget);
202 
203     if (mainNB->get_n_pages() > 1 && page_num <= (filesEdited.size() - 1)) {
204         set_title_decorated(ep->getFileName());
205     }
206 
207     ep->setAspect();
208     //}
209 }
210 
addEditorPanel(EditorPanel * ep,const std::string & name)211 void EditWindow::addEditorPanel (EditorPanel* ep, const std::string &name)
212 {
213     ep->setParent (parent);
214     ep->setParentWindow(this);
215 
216     // construct closeable tab for the image
217     Gtk::HBox* hb = Gtk::manage (new Gtk::HBox ());
218     hb->pack_start (*Gtk::manage (new RTImage ("aperture.png")));
219     hb->pack_start (*Gtk::manage (new Gtk::Label (Glib::path_get_basename (name))));
220     hb->set_tooltip_markup (name);
221     Gtk::Button* closeb = Gtk::manage (new Gtk::Button ());
222     closeb->set_image (*Gtk::manage(new RTImage ("cancel-small.png")));
223     closeb->set_relief (Gtk::RELIEF_NONE);
224     closeb->set_focus_on_click (false);
225 
226     // make the button as small as possible thanks via css
227     closeb->set_name("notebook_close_button");
228 
229     closeb->signal_clicked().connect( sigc::bind (sigc::mem_fun(*this, &EditWindow::remEditorPanel) , ep));
230     hb->pack_end (*closeb);
231     hb->set_spacing (2);
232     hb->show_all ();
233 
234     mainNB->append_page (*ep, *hb);
235     mainNB->set_current_page (mainNB->page_num (*ep));
236     mainNB->set_tab_reorderable (*ep, true);
237 
238     set_title_decorated(name);
239 
240     epanels[ name ] = ep;
241     filesEdited.insert ( name );
242     parent->fpanel->refreshEditedState (filesEdited);
243     ep->setAspect();
244 }
245 
remEditorPanel(EditorPanel * ep)246 void EditWindow::remEditorPanel (EditorPanel* ep)
247 {
248     if (ep->getIsProcessing()) {
249         return;    // Will crash if destroyed while loading
250     }
251 
252     epanels.erase (ep->getFileName());
253     filesEdited.erase (ep->getFileName ());
254     parent->fpanel->refreshEditedState (filesEdited);
255 
256     mainNB->remove_page (*ep);
257 
258     if (mainNB->get_n_pages() > 0) {
259         EditorPanel* ep1 = static_cast<EditorPanel*>(mainNB->get_nth_page (mainNB->get_current_page()));
260         set_title_decorated(ep1->getFileName());
261     } else {
262         set_title_decorated("");
263     }
264 
265     // TODO: save options if wanted
266 }
267 
selectEditorPanel(const std::string & name)268 bool EditWindow::selectEditorPanel(const std::string &name)
269 {
270     std::map<Glib::ustring, EditorPanel*>::iterator iep = epanels.find(name);
271 
272     if (iep != epanels.end()) {
273         mainNB->set_current_page (mainNB->page_num (*iep->second));
274         set_title_decorated(name);
275         return true;
276     }
277 
278     return false;
279 }
280 
toFront()281 void EditWindow::toFront ()
282 {
283     // when using the secondary window on the same monitor as the primary window we need to present the secondary window.
284     // If we don't, it will stay in background when opening 2nd, 3rd... editor, which is annoying
285     // It will also deiconify the window
286     present();
287 }
288 
keyPressed(GdkEventKey * event)289 bool EditWindow::keyPressed (GdkEventKey* event)
290 {
291     bool ctrl = event->state & GDK_CONTROL_MASK;
292 
293     if(event->keyval == GDK_KEY_F11) {
294         toggleFullscreen();
295         return true;
296     } else {
297         if(mainNB->get_n_pages () > 0) { //pass the handling for the editor panels, if there are any
298             if (event->keyval == GDK_KEY_w && ctrl) { //remove editor panel
299                 EditorPanel* ep = static_cast<EditorPanel*>(mainNB->get_nth_page (mainNB->get_current_page()));
300                 remEditorPanel (ep);
301                 return true;
302             } else if(mainNB->get_n_pages () > 0) {
303                 EditorPanel* ep = static_cast<EditorPanel*>(mainNB->get_nth_page (mainNB->get_current_page()));
304                 return ep->handleShortcutKey (event);
305             }
306         }
307 
308         return false;
309     }
310 
311 }
312 
toggleFullscreen()313 void EditWindow::toggleFullscreen ()
314 {
315     isFullscreen ? unfullscreen() : fullscreen();
316     options.meowFullScreen = isFullscreen = !isFullscreen;
317 }
318 
writeOptions()319 void EditWindow::writeOptions() {
320 
321     if(is_visible()) {
322         if(isMultiDisplayEnabled()) {
323             options.meowMonitor = get_screen()->get_monitor_at_window(get_window());
324         }
325 
326         options.meowMaximized = is_maximized();
327         get_position(options.meowX, options.meowY);
328         get_size(options.meowWidth,options.meowHeight);
329     }
330 }
on_delete_event(GdkEventAny * event)331 bool EditWindow::on_delete_event(GdkEventAny* event)
332 {
333 
334     if (!closeOpenEditors()) {
335         return true;
336     }
337 
338     writeOptions();
339     hide();
340     isClosed = true;
341 
342     return false;
343 }
344 
isProcessing()345 bool EditWindow::isProcessing ()
346 {
347     for ( std::set <Glib::ustring>::iterator iter = filesEdited.begin(); iter != filesEdited.end(); ++iter ) {
348         if (epanels[*iter]->getIsProcessing()) {
349             return true;
350         }
351     }
352 
353     return false;
354 }
355 
closeOpenEditors()356 bool EditWindow::closeOpenEditors()
357 {
358     // Check if any editor is still processing, and do NOT quit if so. Otherwise crashes and inconsistent caches
359     if (isProcessing()) {
360         return false;
361     }
362 
363     if (epanels.size()) {
364         int page = mainNB->get_current_page();
365         Gtk::Widget *w = mainNB->get_nth_page(page);
366         bool optionsWritten = false;
367 
368         for (std::map<Glib::ustring, EditorPanel*>::iterator i = epanels.begin(); i != epanels.end(); ++i) {
369             if (i->second == w) {
370                 i->second->writeOptions();
371                 optionsWritten = true;
372             }
373         }
374 
375         if (!optionsWritten) {
376             // fallback solution: save the options of the first editor panel
377             std::map<Glib::ustring, EditorPanel*>::iterator i = epanels.begin();
378             i->second->writeOptions();
379         }
380     }
381 
382     for ( std::set <Glib::ustring>::iterator iter = filesEdited.begin(); iter != filesEdited.end(); ++iter ) {
383         mainNB->remove_page (*epanels[*iter]);
384     }
385 
386     epanels.clear();
387     filesEdited.clear();
388     parent->fpanel->refreshEditedState (filesEdited);
389 
390     return true;
391 }
392 
set_title_decorated(Glib::ustring fname)393 void EditWindow::set_title_decorated(Glib::ustring fname)
394 {
395     Glib::ustring subtitle;
396 
397     if (!fname.empty()) {
398         subtitle = " - " + fname;
399     }
400 
401     set_title(Glib::ustring(RTNAME " ") + M("EDITWINDOW_TITLE") + subtitle);
402 }
403