diff --git a/gajim/common/app.py b/gajim/common/app.py
index 6a39c1a8e..2506a921e 100644
--- a/gajim/common/app.py
+++ b/gajim/common/app.py
@@ -155,6 +155,7 @@ _dependencies = {
'PYCURL': False,
'GSPELL': False,
'IDLE': False,
+ 'RUN_AS_FLATPAK': False,
}
@@ -167,6 +168,9 @@ def is_installed(dependency):
return _dependencies['AVAHI'] or _dependencies['PYBONJOUR']
return _dependencies[dependency]
+def is_flatpak():
+ return _dependencies['RUN_AS_FLATPAK']
+
def disable_dependency(dependency):
_dependencies[dependency] = False
diff --git a/gajim/data/gui/history_manager.ui b/gajim/data/gui/history_manager.ui
index a303efb58..5142d4204 100644
--- a/gajim/data/gui/history_manager.ui
+++ b/gajim/data/gui/history_manager.ui
@@ -1,5 +1,5 @@
-
+
-
True
False
@@ -98,6 +34,9 @@
650
500
+
+
+
True
@@ -306,8 +245,5 @@ If you plan to do massive deletions, please make sure Gajim is not running. Gene
-
-
-
diff --git a/gajim/data/gui/profile_window.ui b/gajim/data/gui/profile_window.ui
index 0a6fe161e..7893928d9 100644
--- a/gajim/data/gui/profile_window.ui
+++ b/gajim/data/gui/profile_window.ui
@@ -1,5 +1,5 @@
-
+
@@ -8,6 +8,9 @@
dialog
+
+
+
True
@@ -30,10 +33,10 @@
True
False
- 0
- 0
<b>Name:</b>
True
+ 0
+ 0
0
@@ -54,10 +57,10 @@
True
False
- 1
- 0
<b>Nickname:</b>
True
+ 1
+ 0
2
@@ -89,10 +92,10 @@
True
False
- 1
- 0
<b>Family:</b>
True
+ 1
+ 0
0
@@ -113,10 +116,10 @@
True
False
- 1
- 0
<b>Given:</b>
True
+ 1
+ 0
2
@@ -137,10 +140,10 @@
True
False
- 1
- 0
<b>Middle:</b>
True
+ 1
+ 0
0
@@ -151,10 +154,10 @@
True
False
- 1
- 0
<b>Prefix:</b>
True
+ 1
+ 0
0
@@ -185,10 +188,10 @@
True
False
- 1
- 0
<b>Suffix:</b>
True
+ 1
+ 0
2
@@ -217,10 +220,10 @@
True
False
- 0
- 0
<b>Full Name</b>
True
+ 0
+ 0
@@ -245,10 +248,10 @@
True
False
- 1
- 0
<b>Street:</b>
True
+ 1
+ 0
0
@@ -269,10 +272,10 @@
True
False
- 1
- 0
<b>Extra Address:</b>
True
+ 1
+ 0
2
@@ -293,10 +296,10 @@
True
False
- 1
- 0
<b>City:</b>
True
+ 1
+ 0
0
@@ -317,10 +320,10 @@
True
False
- 1
- 0
<b>Postal Code:</b>
True
+ 1
+ 0
2
@@ -341,10 +344,10 @@
True
False
- 1
- 0
<b>State:</b>
True
+ 1
+ 0
0
@@ -365,10 +368,10 @@
True
False
- 1
- 0
<b>Country:</b>
True
+ 1
+ 0
2
@@ -391,10 +394,10 @@
True
False
- 0
- 0
<b>Address</b>
True
+ 0
+ 0
@@ -408,10 +411,10 @@
True
False
- 1
- 0
<b>Homepage:</b>
True
+ 1
+ 0
0
@@ -433,9 +436,9 @@
True
False
- 1
<b>E-Mail:</b>
True
+ 1
0
@@ -457,10 +460,10 @@
True
False
- 1
- 0
<b>Phone No.:</b>
True
+ 1
+ 0
0
@@ -492,10 +495,10 @@
True
False
- 1
- 0
<b>Avatar:</b>
True
+ 1
+ 0
0
@@ -506,12 +509,11 @@
True
False
+ 6
True
False
- 0
- 0
@@ -522,7 +524,6 @@
True
False
- 0
gtk-missing-image
@@ -543,8 +544,6 @@
False
True
True
- 0
- 0
@@ -553,10 +552,31 @@
1
+
+
+ Remove Avatar
+ True
+ True
+ True
+ Clear Avatar
+ start
+ end
+
+
+
+
+ False
+ True
+ 2
+
+
1
6
+ 3
@@ -569,10 +589,10 @@
True
False
- 1
- 0
<b>Birthday:</b>
True
+ 1
+ 0
@@ -581,21 +601,15 @@
5
-
-
-
-
-
-
True
False
+ Personal Info
0
0
- Personal Info
False
@@ -612,10 +626,10 @@
True
False
- 1
- 0
<b>Company:</b>
True
+ 1
+ 0
0
@@ -636,10 +650,10 @@
True
False
- 1
- 0
<b>Department:</b>
True
+ 1
+ 0
2
@@ -660,10 +674,10 @@
True
False
- 1
- 0
<b>Position:</b>
True
+ 1
+ 0
0
@@ -684,10 +698,10 @@
True
False
- 1
- 0
<b>Role:</b>
True
+ 1
+ 0
2
@@ -719,10 +733,10 @@
True
False
- 1
- 0
<b>Street:</b>
True
+ 1
+ 0
0
@@ -743,10 +757,10 @@
True
False
- 1
- 0
<b>Extra Address:</b>
True
+ 1
+ 0
2
@@ -767,10 +781,10 @@
True
False
- 1
- 0
<b>City:</b>
True
+ 1
+ 0
0
@@ -791,10 +805,10 @@
True
False
- 1
- 0
<b>Postal Code:</b>
True
+ 1
+ 0
2
@@ -815,10 +829,10 @@
True
False
- 1
- 0
<b>State:</b>
True
+ 1
+ 0
0
@@ -839,10 +853,10 @@
True
False
- 1
- 0
<b>Country:</b>
True
+ 1
+ 0
2
@@ -865,10 +879,10 @@
True
False
- 1
- 0
<b>Address</b>
True
+ 1
+ 0
@@ -882,10 +896,10 @@
True
False
- 1
- 0
<b>E-Mail:</b>
True
+ 1
+ 0
0
@@ -907,10 +921,10 @@
True
False
- 1
- 0
<b>Phone No.:</b>
True
+ 1
+ 0
0
@@ -942,9 +956,9 @@
True
False
+ Work
0
0
- Work
1
@@ -974,9 +988,9 @@
True
False
+ About
0
0
- About
2
diff --git a/gajim/data/gui/send_file_dialog.ui b/gajim/data/gui/send_file_dialog.ui
new file mode 100644
index 000000000..48c7dc24a
--- /dev/null
+++ b/gajim/data/gui/send_file_dialog.ui
@@ -0,0 +1,116 @@
+
+
+
+
+
+ True
+ False
+ 6
+ 6
+
+
+ True
+ True
+ True
+ never
+ in
+
+
+ True
+ False
+
+
+ True
+ False
+ True
+ none
+ False
+
+
+
+
+
+
+ 0
+ 1
+ 2
+
+
+
+
+ True
+ False
+ Description:
+ 0
+
+
+ 0
+ 2
+ 2
+
+
+
+
+ 40
+ True
+ True
+ True
+ never
+ in
+
+
+ True
+ True
+ word-char
+
+
+
+
+ 0
+ 3
+ 2
+
+
+
+
+ Select Files
+ True
+ True
+ True
+ start
+
+
+
+ 0
+ 4
+
+
+
+
+ Send
+ True
+ True
+ True
+ end
+
+
+
+ 1
+ 4
+
+
+
+
+ True
+ False
+ Files:
+ 0
+
+
+ 0
+ 0
+ 2
+
+
+
+
diff --git a/gajim/data/style/gajim.css b/gajim/data/style/gajim.css
index 57e28c170..50f349476 100644
--- a/gajim/data/style/gajim.css
+++ b/gajim/data/style/gajim.css
@@ -99,3 +99,7 @@ popover#EmoticonPopover flowboxchild { padding-top: 5px; padding-bottom: 5px; }
/*MessageWindow Notebook*/
.notebook-tab-label {min-width: 80px}
+
+/*SendFileDialog*/
+#SendFileDialog grid {padding: 12px}
+#SendFileDialog grid list { background-color: @theme_bg_color}
diff --git a/gajim/dialogs.py b/gajim/dialogs.py
index 00dad6d8f..577563cef 100644
--- a/gajim/dialogs.py
+++ b/gajim/dialogs.py
@@ -1426,74 +1426,6 @@ class HigDialog(Gtk.MessageDialog):
vb.grab_focus()
self.show_all()
-class FileChooserDialog(Gtk.FileChooserDialog):
- """
- Non-blocking FileChooser Dialog around Gtk.FileChooserDialog
- """
- def __init__(self, title_text, action, buttons, default_response,
- select_multiple=False, current_folder=None, on_response_ok=None,
- on_response_cancel=None, preview=False, transient_for=None):
-
- Gtk.FileChooserDialog.__init__(self, title=title_text,
- parent=transient_for, action=action)
- self.add_button(buttons[0],buttons[1])
- if len(buttons) ==4:
- self.add_button(buttons[2],buttons[3])
- self.set_default_response(default_response)
- self.set_select_multiple(select_multiple)
- if current_folder and os.path.isdir(current_folder):
- self.set_current_folder(current_folder)
- else:
- self.set_current_folder(os.path.expanduser('~'))
- self.response_ok, self.response_cancel = \
- on_response_ok, on_response_cancel
- # in gtk+-2.10 clicked signal on some of the buttons in a dialog
- # is emitted twice, so we cannot rely on 'clicked' signal
- self.connect('response', self.on_dialog_response)
-
- if preview:
- self.set_use_preview_label(False)
- self.set_preview_widget(Gtk.Image())
- self.connect('selection-changed', self.update_preview)
-
- self.show_all()
-
- def on_dialog_response(self, dialog, response):
- if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.CLOSE):
- if self.response_cancel:
- if isinstance(self.response_cancel, tuple):
- self.response_cancel[0](dialog, *self.response_cancel[1:])
- else:
- self.response_cancel(dialog)
- else:
- self.just_destroy(dialog)
- elif response == Gtk.ResponseType.OK:
- if self.response_ok:
- if isinstance(self.response_ok, tuple):
- self.response_ok[0](dialog, *self.response_ok[1:])
- else:
- self.response_ok(dialog)
- else:
- self.just_destroy(dialog)
-
- def update_preview(self, widget):
- path_to_file = widget.get_preview_filename()
- preview = widget.get_preview_widget()
- if path_to_file is None or os.path.isdir(path_to_file):
- # nothing to preview or directory
- # make sure you clean image do show nothing
- preview.clear()
- return
- try:
- pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(path_to_file, 200, 200)
- except GObject.GError:
- preview.clear()
- return
- widget.get_preview_widget().set_from_pixbuf(pixbuf)
-
- def just_destroy(self, widget):
- self.destroy()
-
class AspellDictError:
def __init__(self, lang):
ErrorDialog(
@@ -4773,139 +4705,6 @@ class ProgressDialog:
def on_progress_dialog_delete_event(self, widget, event):
return True # WM's X button or Escape key should not destroy the window
-class ImageChooserDialog(FileChooserDialog):
- def __init__(self, path_to_file='', on_response_ok=None,
- on_response_cancel=None):
- """
- Optionally accepts path_to_snd_file so it has that as selected
- """
- def on_ok(widget, callback):
- '''check if file exists and call callback'''
- path_to_file = self.get_filename()
- if not path_to_file:
- return
- if os.path.exists(path_to_file):
- if isinstance(callback, tuple):
- callback[0](widget, path_to_file, *callback[1:])
- else:
- callback(widget, path_to_file)
-
- path = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES)
- FileChooserDialog.__init__(self,
- title_text = _('Choose Image'),
- action = Gtk.FileChooserAction.OPEN,
- buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_OPEN, Gtk.ResponseType.OK),
- default_response = Gtk.ResponseType.OK,
- current_folder = path,
- on_response_ok = (on_ok, on_response_ok),
- on_response_cancel = on_response_cancel)
-
- if on_response_cancel:
- self.connect('destroy', on_response_cancel)
-
- filter_ = Gtk.FileFilter()
- filter_.set_name(_('All files'))
- filter_.add_pattern('*')
- self.add_filter(filter_)
-
- filter_ = Gtk.FileFilter()
- filter_.set_name(_('Images'))
- filter_.add_mime_type('image/png')
- filter_.add_mime_type('image/jpeg')
- filter_.add_mime_type('image/gif')
- filter_.add_mime_type('image/tiff')
- filter_.add_mime_type('image/svg+xml')
- filter_.add_mime_type('image/x-xpixmap') # xpm
- self.add_filter(filter_)
- self.set_filter(filter_)
-
- if path_to_file:
- self.set_filename(path_to_file)
-
- self.set_use_preview_label(False)
- self.set_preview_widget(Gtk.Image())
- self.connect('selection-changed', self.update_preview)
-
- def update_preview(self, widget):
- path_to_file = widget.get_preview_filename()
- if path_to_file is None or os.path.isdir(path_to_file):
- # nothing to preview or directory
- # make sure you clean image do show nothing
- preview = widget.get_preview_widget()
- preview.clear()
- return
- try:
- pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(path_to_file, 100, 100)
- except GObject.GError:
- return
- widget.get_preview_widget().set_from_pixbuf(pixbuf)
-
-class AvatarChooserDialog(ImageChooserDialog):
- def __init__(self, path_to_file='', on_response_ok=None,
- on_response_cancel=None, on_response_clear=None):
- ImageChooserDialog.__init__(self, path_to_file, on_response_ok,
- on_response_cancel)
- button = Gtk.Button(None, Gtk.STOCK_CLEAR)
- self.response_clear = on_response_clear
- if on_response_clear:
- button.connect('clicked', self.on_clear)
- button.show_all()
- action_area = self.get_action_area()
- action_area.pack_start(button, True, True, 0)
- action_area.reorder_child(button, 0)
-
- def on_clear(self, widget):
- if isinstance(self.response_clear, tuple):
- self.response_clear[0](widget, *self.response_clear[1:])
- else:
- self.response_clear(widget)
-
-
-class ArchiveChooserDialog(FileChooserDialog):
- def __init__(self, on_response_ok=None, on_response_cancel=None,
- transient_for=None):
-
- def on_ok(widget, callback):
- '''check if file exists and call callback'''
- path_to_file = self.get_filename()
- if not path_to_file:
- return
- if os.path.exists(path_to_file):
- if isinstance(callback, tuple):
- callback[0](path_to_file, *callback[1:])
- else:
- callback(path_to_file)
- self.destroy()
-
- path = os.path.expanduser('~')
-
- FileChooserDialog.__init__(self,
- title_text=_('Choose Archive'),
- action=Gtk.FileChooserAction.OPEN,
- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_OPEN, Gtk.ResponseType.OK),
- default_response=Gtk.ResponseType.OK,
- current_folder=path,
- on_response_ok=(on_ok, on_response_ok),
- on_response_cancel=on_response_cancel,
- transient_for=transient_for)
-
- if on_response_cancel:
- self.connect('destroy', on_response_cancel)
-
- filter_ = Gtk.FileFilter()
- filter_.set_name(_('All files'))
- filter_.add_pattern('*')
- self.add_filter(filter_)
-
- filter_ = Gtk.FileFilter()
- filter_.set_name(_('Zip files'))
- filter_.add_pattern('*.zip')
-
- self.add_filter(filter_)
- self.set_filter(filter_)
-
class TransformChatToMUC:
# Keep a reference on windows so garbage collector don't restroy them
instances = []
diff --git a/gajim/filechoosers.py b/gajim/filechoosers.py
new file mode 100644
index 000000000..ea79bb411
--- /dev/null
+++ b/gajim/filechoosers.py
@@ -0,0 +1,212 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2018 Philipp Hörist
+#
+# This file is part of Gajim.
+#
+# Gajim 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.
+#
+# Gajim 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 Gajim. If not, see .
+
+import os
+from pathlib import Path
+from collections import namedtuple
+
+from gi.repository import Gtk
+from gi.repository import GdkPixbuf
+from gi.repository import GObject
+
+from gajim.common import app
+
+Filter = namedtuple('Filter', 'name pattern default')
+
+
+# Notes: Adding mime types to Gtk.FileFilter forces non-native dialogs
+
+class BaseFileChooser:
+ def _on_response(self, dialog, response, accept_cb, cancel_cb):
+ if response == Gtk.ResponseType.ACCEPT:
+ if self.get_select_multiple():
+ accept_cb(dialog.get_filenames())
+ else:
+ accept_cb(dialog.get_filename())
+
+ if response in (Gtk.ResponseType.CANCEL,
+ Gtk.ResponseType.DELETE_EVENT):
+ if cancel_cb is not None:
+ cancel_cb()
+
+ def _add_filters(self, filters):
+ for filterinfo in filters:
+ filter_ = Gtk.FileFilter()
+ filter_.set_name(filterinfo.name)
+ if isinstance(filterinfo.pattern, list):
+ for mime_type in filterinfo.pattern:
+ filter_.add_mime_type(mime_type)
+ else:
+ filter_.add_pattern(filterinfo.pattern)
+ self.add_filter(filter_)
+ if filterinfo.default:
+ self.set_filter(filter_)
+
+ def _update_preview(self, filechooser):
+ path_to_file = filechooser.get_preview_filename()
+ preview = filechooser.get_preview_widget()
+ if path_to_file is None or os.path.isdir(path_to_file):
+ # nothing to preview
+ preview.clear()
+ return
+ try:
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
+ path_to_file, *self._preivew_size)
+ except GObject.GError:
+ preview.clear()
+ return
+ filechooser.get_preview_widget().set_from_pixbuf(pixbuf)
+
+
+class BaseFileOpenDialog:
+
+ _title = _('Choose File to Send…')
+ _filters = [Filter(_('All files'), '*', True)]
+
+
+class BaseAvatarChooserDialog:
+
+ _title = _('Choose Avatar…')
+ _preivew_size = (100, 100)
+
+ if app.is_flatpak():
+ _filters = [Filter(_('PNG files'), '*.png', True),
+ Filter(_('JPEG files'), '*.jp*g', False),
+ Filter(_('SVG files'), '*.svg', False)]
+ else:
+ _filters = [Filter(_('Images'), ['image/png',
+ 'image/jpeg',
+ 'image/svg+xml'], True)]
+
+
+class NativeFileChooserDialog(Gtk.FileChooserNative, BaseFileChooser):
+
+ _title = ''
+ _filters = []
+ _action = Gtk.FileChooserAction.OPEN
+
+ def __init__(self, accept_cb, cancel_cb=None, transient_for=None,
+ path=None, file_name=None, select_multiple=False,
+ modal=False):
+
+ Gtk.FileChooserNative.__init__(self,
+ title=self._title,
+ action=self._action,
+ transient_for=transient_for)
+
+ self.set_current_folder(path or str(Path.home()))
+ if file_name is not None:
+ self.set_current_name(file_name)
+ self.set_select_multiple(select_multiple)
+ self.set_do_overwrite_confirmation(True)
+ self.set_modal(modal)
+ self._add_filters(self._filters)
+
+ self.connect('response', self._on_response, accept_cb, cancel_cb)
+ self.show()
+
+
+class ArchiveChooserDialog(NativeFileChooserDialog):
+
+ _title = _('Choose Archive')
+ _filters = [Filter(_('All files'), '*', False),
+ Filter(_('ZIP files'), '*.zip', True)]
+
+
+class FileSaveDialog(NativeFileChooserDialog):
+
+ _title = _('Save File as…')
+ _filters = [Filter(_('All files'), '*', True)]
+ _action = Gtk.FileChooserAction.SAVE
+
+
+class AvatarSaveDialog(FileSaveDialog):
+
+ if os.name == 'nt':
+ _filters = [Filter(_('Images'), '*.png;*.jpg;*.jpeg;*.svg', True)]
+
+
+class NativeFileOpenDialog(BaseFileOpenDialog, NativeFileChooserDialog):
+ pass
+
+
+class NativeAvatarChooserDialog(BaseAvatarChooserDialog, NativeFileChooserDialog):
+ pass
+
+
+class GtkFileChooserDialog(Gtk.FileChooserDialog, BaseFileChooser):
+
+ _title = ''
+ _filters = []
+ _action = Gtk.FileChooserAction.OPEN
+ _preivew_size = (200, 200)
+
+ def __init__(self, accept_cb, cancel_cb=None, transient_for=None,
+ path=None, file_name=None, select_multiple=False,
+ preview=True, modal=False):
+
+ Gtk.FileChooserDialog.__init__(
+ self,
+ title=self._title,
+ action=self._action,
+ buttons=[Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
+ Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT],
+ transient_for=transient_for)
+
+ self.set_current_folder(path or str(Path.home()))
+ if file_name is not None:
+ self.set_current_name(file_name)
+ self.set_select_multiple(select_multiple)
+ self.set_do_overwrite_confirmation(True)
+ self.set_modal(modal)
+ self._add_filters(self._filters)
+
+ if preview:
+ self.set_use_preview_label(False)
+ self.set_preview_widget(Gtk.Image())
+ self.connect('selection-changed', self._update_preview)
+
+ self.connect('response', self._on_response, accept_cb, cancel_cb)
+ self.show()
+
+ def _on_response(self, *args):
+ super()._on_response(*args)
+ self.destroy()
+
+
+class GtkFileOpenDialog(BaseFileOpenDialog, GtkFileChooserDialog):
+ pass
+
+
+class GtkAvatarChooserDialog(BaseAvatarChooserDialog, GtkFileChooserDialog):
+ pass
+
+
+def FileChooserDialog(*args, **kwargs):
+ if app.is_flatpak():
+ return NativeFileOpenDialog(*args, **kwargs)
+ else:
+ return GtkFileOpenDialog(*args, **kwargs)
+
+
+def AvatarChooserDialog(*args, **kwargs):
+ if app.is_flatpak():
+ return NativeAvatarChooserDialog(*args, **kwargs)
+ else:
+ return GtkAvatarChooserDialog(*args, **kwargs)
diff --git a/gajim/filetransfers_window.py b/gajim/filetransfers_window.py
index ef1a0b75a..11744c758 100644
--- a/gajim/filetransfers_window.py
+++ b/gajim/filetransfers_window.py
@@ -28,6 +28,8 @@ from gi.repository import GLib
from gi.repository import Pango
import os
import time
+from functools import partial
+from pathlib import Path
from enum import IntEnum, unique
from datetime import datetime
@@ -41,6 +43,7 @@ from gajim.common import helpers
from gajim.common.file_props import FilesProp
from gajim.common.protocol.bytestream import (is_transfer_active, is_transfer_paused,
is_transfer_stopped)
+from gajim.filechoosers import FileSaveDialog, FileChooserDialog
from nbxmpp.protocol import NS_JINGLE_FILE_TRANSFER_5
import logging
log = logging.getLogger('gajim.filetransfer_window')
@@ -322,51 +325,8 @@ class FileTransfersWindow:
account), type_=Gtk.MessageType.ERROR)
def show_file_send_request(self, account, contact):
- win = Gtk.ScrolledWindow()
- win.set_shadow_type(Gtk.ShadowType.IN)
- win.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER)
-
- from gajim.message_textview import MessageTextView
- desc_entry = MessageTextView()
- win.add(desc_entry)
-
- def on_ok(widget):
- file_dir = None
- files_path_list = dialog.get_filenames()
- desc = desc_entry.get_text()
- for file_path in files_path_list:
- if self.send_file(account, contact, file_path, desc) \
- and file_dir is None:
- file_dir = os.path.dirname(file_path)
- if file_dir:
- app.config.set('last_send_dir', file_dir)
- dialog.destroy()
-
- dialog = dialogs.FileChooserDialog(_('Choose File to Send…'),
- Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL),
- Gtk.ResponseType.OK,
- True, # select multiple true as we can select many files to send
- app.config.get('last_send_dir'),
- on_response_ok=on_ok,
- on_response_cancel=lambda e:dialog.destroy(),
- preview=True,
- transient_for=app.interface.roster.window
- )
-
- btn = Gtk.Button.new_with_mnemonic(_('_Send'))
- btn.set_property('can-default', True)
- # FIXME: add send icon to this button (JUMP_TO)
- dialog.add_action_widget(btn, Gtk.ResponseType.OK)
- dialog.set_default_response(Gtk.ResponseType.OK)
-
- desc_hbox = Gtk.HBox(homogeneous=False, spacing=5)
- desc_hbox.pack_start(Gtk.Label.new(_('Description: ')), False, False, 0)
- desc_hbox.pack_start(win, True, True, 0)
-
- dialog.vbox.pack_start(desc_hbox, False, False, 0)
-
- btn.show()
- desc_hbox.show_all()
+ send_callback = partial(self.send_file, account, contact)
+ SendFileDialog(send_callback, self.window)
def send_file(self, account, contact, file_path, file_desc=''):
"""
@@ -411,9 +371,9 @@ class FileTransfersWindow:
app.connections[account].send_file_approval(file_props)
def on_file_request_accepted(self, account, contact, file_props):
- def on_ok(widget, account, contact, file_props):
- file_path = dialog2.get_filename()
+ def on_ok(account, contact, file_props, file_path):
if os.path.exists(file_path):
+ app.config.set('last_save_dir', os.path.dirname(file_path))
# check if we have write permissions
if not os.access(file_path, os.W_OK):
file_name = GLib.markup_escape_text(os.path.basename(
@@ -433,13 +393,11 @@ class FileTransfersWindow:
return
elif response == 100:
file_props.offset = dl_size
- dialog2.destroy()
self._start_receive(file_path, account, contact, file_props)
dialog = dialogs.FTOverwriteConfirmationDialog(
_('This file already exists'), _('What do you want to do?'),
- propose_resume=not dl_finished, on_response=on_response,
- transient_for=dialog2)
+ propose_resume=not dl_finished, on_response=on_response)
dialog.set_destroy_with_parent(True)
return
else:
@@ -452,26 +410,15 @@ class FileTransfersWindow:
dirname, _('You do not have permission to create files '
'in this directory.'))
return
- dialog2.destroy()
self._start_receive(file_path, account, contact, file_props)
- def on_cancel(widget, account, contact, file_props):
- dialog2.destroy()
- app.connections[account].send_file_rejection(file_props)
-
- dialog2 = dialogs.FileChooserDialog(
- title_text=_('Save File as…'),
- action=Gtk.FileChooserAction.SAVE,
- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_SAVE, Gtk.ResponseType.OK),
- default_response=Gtk.ResponseType.OK,
- current_folder=app.config.get('last_save_dir'),
- on_response_ok=(on_ok, account, contact, file_props),
- on_response_cancel=(on_cancel, account, contact, file_props))
-
- dialog2.set_current_name(file_props.name)
- dialog2.connect('delete-event', lambda widget, event:
- on_cancel(widget, account, contact, file_props))
+ con = app.connections[account]
+ accept_cb = partial(on_ok, account, contact, file_props)
+ cancel_cb = partial(con.send_file_rejection, file_props)
+ FileSaveDialog(accept_cb,
+ cancel_cb,
+ path=app.config.get('last_save_dir'),
+ file_name=file_props.name)
def show_file_request(self, account, contact, file_props):
"""
@@ -1048,3 +995,78 @@ class FileTransfersWindow:
def on_file_transfers_window_key_press_event(self, widget, event):
if event.keyval == Gdk.KEY_Escape: # ESCAPE
self.window.hide()
+
+
+class SendFileDialog(Gtk.ApplicationWindow):
+ def __init__(self, send_callback, transient_for):
+ active_window = app.app.get_active_window()
+ Gtk.ApplicationWindow.__init__(self)
+ self.set_name('SendFileDialog')
+ self.set_application(app.app)
+ self.set_show_menubar(False)
+ self.set_resizable(True)
+ self.set_default_size(400, 250)
+ self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
+ self.set_transient_for(active_window)
+ self.set_title(_('Choose a File to Send…'))
+ self.set_destroy_with_parent(True)
+
+ self._send_callback = send_callback
+
+ xml = gtkgui_helpers.get_gtk_builder('send_file_dialog.ui')
+ grid = xml.get_object('send_file_grid')
+
+ self._filebox = xml.get_object('listbox')
+ self._description = xml.get_object('description')
+
+ self.add(grid)
+ self.connect('key-press-event', self._key_press_event)
+
+ xml.connect_signals(self)
+ self.show_all()
+
+ def _send(self, button):
+ for file in self._filebox.get_children():
+ self._send_callback(str(file.path), self._get_description())
+ self.destroy()
+
+ def _select_files(self, button):
+ FileChooserDialog(self._set_files,
+ select_multiple=True,
+ transient_for=self,
+ path=app.config.get('last_send_dir'))
+
+ def _set_files(self, filenames):
+ # Clear the ListBox
+ self._filebox.foreach(self._remove_widget, None)
+
+ for file in filenames:
+ row = FileRow(file)
+ if row.path.is_dir():
+ continue
+ last_dir = row.path.parent
+ self._filebox.add(row)
+ self._filebox.show_all()
+ app.config.set('last_send_dir', str(last_dir))
+
+ def _remove_widget(self, widget, data):
+ self._filebox.remove(widget)
+
+ def _get_description(self):
+ buffer_ = self._description.get_buffer()
+ start, end = buffer_.get_bounds()
+ return buffer_.get_text(start, end, False)
+
+ def _key_press_event(self, widget, event):
+ if event.keyval == Gdk.KEY_Escape:
+ self.destroy()
+
+
+class FileRow(Gtk.ListBoxRow):
+ def __init__(self, path):
+ Gtk.ListBoxRow.__init__(self)
+ self.path = Path(path)
+ label = Gtk.Label(self.path.name)
+ label.set_ellipsize(Pango.EllipsizeMode.END)
+ label.set_xalign(0)
+ self.add(label)
diff --git a/gajim/gtkgui_helpers.py b/gajim/gtkgui_helpers.py
index ea056d007..3a6fd0c2b 100644
--- a/gajim/gtkgui_helpers.py
+++ b/gajim/gtkgui_helpers.py
@@ -51,6 +51,7 @@ from gajim.common import i18n
from gajim.common import app
from gajim.common import pep
from gajim.common import configpaths
+from gajim.filechoosers import AvatarSaveDialog
gtk_icon_theme = Gtk.IconTheme.get_default()
gtk_icon_theme.append_search_path(configpaths.get('ICONS'))
@@ -450,6 +451,7 @@ def on_avatar_save_as_menuitem_activate(widget, avatar, default_name=''):
if response < 0:
return
+ app.config.set('last_save_dir', os.path.dirname(file_path))
if isinstance(avatar, str):
# We got a SHA
pixbuf = app.interface.get_avatar(avatar)
@@ -459,10 +461,8 @@ def on_avatar_save_as_menuitem_activate(widget, avatar, default_name=''):
extension = os.path.splitext(file_path)[1]
if not extension:
# Silently save as Jpeg image
- image_format = 'jpeg'
- file_path += '.jpeg'
- elif extension == 'jpg':
- image_format = 'jpeg'
+ image_format = 'png'
+ file_path += '.png'
else:
image_format = extension[1:] # remove leading dot
@@ -473,19 +473,16 @@ def on_avatar_save_as_menuitem_activate(widget, avatar, default_name=''):
log.error('Error saving avatar: %s' % str(e))
if os.path.exists(file_path):
os.remove(file_path)
- new_file_path = '.'.join(file_path.split('.')[:-1]) + '.jpeg'
+ new_file_path = '.'.join(file_path.split('.')[:-1]) + '.png'
def on_ok(file_path, pixbuf):
- pixbuf.savev(file_path, 'jpeg', [], [])
+ pixbuf.savev(file_path, 'png', [], [])
dialogs.ConfirmationDialog(_('Extension not supported'),
_('Image cannot be saved in %(type)s format. Save as '
'%(new_filename)s?') % {'type': image_format,
'new_filename': new_file_path},
on_response_ok = (on_ok, new_file_path, pixbuf))
- else:
- dialog.destroy()
- def on_ok(widget):
- file_path = dialog.get_filename()
+ def on_ok(file_path):
if os.path.exists(file_path):
# check if we have write permissions
if not os.access(file_path, os.W_OK):
@@ -496,8 +493,7 @@ def on_avatar_save_as_menuitem_activate(widget, avatar, default_name=''):
return
dialog2 = dialogs.FTOverwriteConfirmationDialog(
_('This file already exists'), _('What do you want to do?'),
- propose_resume=False, on_response=(on_continue, file_path),
- transient_for=dialog)
+ propose_resume=False, on_response=(on_continue, file_path))
dialog2.set_destroy_with_parent(True)
else:
dirname = os.path.dirname(file_path)
@@ -509,19 +505,11 @@ def on_avatar_save_as_menuitem_activate(widget, avatar, default_name=''):
on_continue(0, file_path)
- def on_cancel(widget):
- dialog.destroy()
-
- dialog = dialogs.FileChooserDialog(title_text=_('Save Image as…'),
- action=Gtk.FileChooserAction.SAVE, buttons=(Gtk.STOCK_CANCEL,
- Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK),
- default_response=Gtk.ResponseType.OK,
- current_folder=app.config.get('last_save_dir'), on_response_ok=on_ok,
- on_response_cancel=on_cancel)
-
- dialog.set_current_name(default_name + '.jpeg')
- dialog.connect('delete-event', lambda widget, event:
- on_cancel(widget))
+ transient = app.app.get_active_window()
+ AvatarSaveDialog(on_ok,
+ path=app.config.get('last_save_dir'),
+ file_name='%s.png' % default_name,
+ transient_for=transient)
def create_combobox(value_list, selected_value = None):
"""
diff --git a/gajim/gui_interface.py b/gajim/gui_interface.py
index 6afc52988..539e22503 100644
--- a/gajim/gui_interface.py
+++ b/gajim/gui_interface.py
@@ -39,6 +39,7 @@ import sys
import re
import time
import hashlib
+from functools import partial
from gi.repository import Gtk
from gi.repository import GdkPixbuf
@@ -66,7 +67,7 @@ from gajim import notify
from gajim import message_control
from gajim.dialog_messages import get_dialog
from gajim.dialogs import ProgressWindow
-from gajim.dialogs import FileChooserDialog
+from gajim.filechoosers import FileChooserDialog
from gajim.chat_control_base import ChatControlBase
from gajim.chat_control import ChatControl
@@ -1147,22 +1148,13 @@ class Interface:
ProgressWindow(file)
def send_httpupload(self, chat_control):
- FileChooserDialog(
- on_response_ok=lambda widget: self.on_file_dialog_ok(widget,
- chat_control),
- title_text=_('Choose file to send'),
- action=Gtk.FileChooserAction.OPEN,
- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_OPEN, Gtk.ResponseType.OK),
- select_multiple=True,
- default_response=Gtk.ResponseType.OK,
- preview=True,
- transient_for=chat_control.parent_win.window)
+ accept_cb = partial(self.on_file_dialog_ok, chat_control)
+ FileChooserDialog(accept_cb,
+ select_multiple=True,
+ transient_for=chat_control.parent_win.window)
@staticmethod
- def on_file_dialog_ok(widget, chat_control):
- paths = widget.get_filenames()
- widget.destroy()
+ def on_file_dialog_ok(chat_control, paths):
con = app.connections[chat_control.account]
groupchat = chat_control.type_id == message_control.TYPE_GC
for path in paths:
diff --git a/gajim/history_manager.py b/gajim/history_manager.py
index bb15aafb2..ae8b72ef9 100644
--- a/gajim/history_manager.py
+++ b/gajim/history_manager.py
@@ -83,10 +83,11 @@ if is_standalone():
configpaths.init()
from gajim.common import app
-from gajim import gtkgui_helpers
from gajim.common.const import JIDConstant, KindConstant
from gajim.common import helpers
from gajim import dialogs
+from gajim.filechoosers import FileSaveDialog
+from gajim import gtkgui_helpers
@unique
@@ -465,24 +466,14 @@ class HistoryManager:
return True
def on_export_menuitem_activate(self, widget):
- xml = gtkgui_helpers.get_gtk_builder('history_manager.ui',
- 'filechooserdialog')
- xml.connect_signals(self)
+ FileSaveDialog(self._on_export,
+ transient_for=self.window,
+ modal=True)
- dlg = xml.get_object('filechooserdialog')
- dlg.set_title(_('Exporting History Logs…'))
- dlg.set_current_folder(configpaths.get('HOME'))
- dlg.props.do_overwrite_confirmation = True
- response = dlg.run()
-
- if response == Gtk.ResponseType.OK: # user want us to export ;)
- liststore, list_of_paths = self.jids_listview.get_selection()\
- .get_selected_rows()
- path_to_file = dlg.get_filename()
- self._export_jids_logs_to_file(liststore, list_of_paths,
- path_to_file)
-
- dlg.destroy()
+ def _on_export(self, filename):
+ liststore, list_of_paths = self.jids_listview.get_selection()\
+ .get_selected_rows()
+ self._export_jids_logs_to_file(liststore, list_of_paths, filename)
def on_delete_menuitem_activate(self, widget, listview):
widget_name = Gtk.Buildable.get_name(listview)
diff --git a/gajim/plugins/gui.py b/gajim/plugins/gui.py
index 349c64936..9f01ffe63 100644
--- a/gajim/plugins/gui.py
+++ b/gajim/plugins/gui.py
@@ -35,7 +35,8 @@ import os
from enum import IntEnum, unique
from gajim import gtkgui_helpers
-from gajim.dialogs import WarningDialog, YesNoDialog, ArchiveChooserDialog
+from gajim.dialogs import WarningDialog, YesNoDialog
+from gajim.filechoosers import ArchiveChooserDialog
from gajim.htmltextview import HtmlTextView
from gajim.common import app
from gajim.common import configpaths
@@ -309,8 +310,7 @@ class PluginsWindow(object):
sel = self.installed_plugins_treeview.get_selection()
sel.select_iter(iter_)
- self.dialog = ArchiveChooserDialog(
- on_response_ok=_try_install, transient_for=self.window)
+ ArchiveChooserDialog(_try_install, transient_for=self.window)
class GajimPluginConfigDialog(Gtk.Dialog):
diff --git a/gajim/profile_window.py b/gajim/profile_window.py
index 462a75735..7469ccd2c 100644
--- a/gajim/profile_window.py
+++ b/gajim/profile_window.py
@@ -33,6 +33,7 @@ import hashlib
from gajim import gtkgui_helpers
from gajim import dialogs
+from gajim.filechoosers import AvatarChooserDialog
from gajim.common.const import AvatarSize
from gajim.common import app
@@ -107,7 +108,7 @@ class ProfileWindow:
if event.keyval == Gdk.KEY_Escape:
self.window.destroy()
- def on_clear_button_clicked(self, widget):
+ def _clear_photo(self, widget):
# empty the image
button = self.xml.get_object('PHOTO_button')
image = button.get_image()
@@ -115,12 +116,14 @@ class ProfileWindow:
button.hide()
text_button = self.xml.get_object('NOPHOTO_button')
text_button.show()
+ remove_avatar = self.xml.get_object('remove_avatar')
+ remove_avatar.hide()
self.avatar_encoded = None
self.avatar_sha = None
self.avatar_mime_type = None
def on_set_avatar_button_clicked(self, widget):
- def on_ok(widget, path_to_file):
+ def on_ok(path_to_file):
with open(path_to_file, 'rb') as file:
data = file.read()
sha = app.interface.save_avatar(data, publish=True)
@@ -129,9 +132,6 @@ class ProfileWindow:
_('Could not load image'), transient_for=self.window)
return
- self.dialog.destroy()
- self.dialog = None
-
scale = self.window.get_scale_factor()
surface = app.interface.get_avatar(sha, AvatarSize.VCARD, scale)
@@ -142,26 +142,15 @@ class ProfileWindow:
text_button = self.xml.get_object('NOPHOTO_button')
text_button.hide()
+ remove_avatar = self.xml.get_object('remove_avatar')
+ remove_avatar.show()
+
self.avatar_sha = sha
publish = app.interface.get_avatar(sha, publish=True)
self.avatar_encoded = base64.b64encode(publish).decode('utf-8')
- self.avatar_mime_type = 'image/jpeg'
+ self.avatar_mime_type = 'image/png'
- def on_clear(widget):
- self.dialog.destroy()
- self.dialog = None
- self.on_clear_button_clicked(widget)
-
- def on_cancel(widget):
- self.dialog.destroy()
- self.dialog = None
-
- if self.dialog:
- self.dialog.present()
- else:
- self.dialog = dialogs.AvatarChooserDialog(
- on_response_ok=on_ok, on_response_cancel=on_cancel,
- on_response_clear=on_clear)
+ AvatarChooserDialog(on_ok, transient_for=self.window)
def on_PHOTO_button_press_event(self, widget, event):
"""
@@ -218,11 +207,13 @@ class ProfileWindow:
button = self.xml.get_object('PHOTO_button')
image = button.get_image()
text_button = self.xml.get_object('NOPHOTO_button')
+ remove_avatar = self.xml.get_object('remove_avatar')
if not 'PHOTO' in vcard_:
# set default image
image.set_from_pixbuf(None)
button.hide()
text_button.show()
+ remove_avatar.hide()
for i in vcard_.keys():
if i == 'PHOTO':
photo_encoded = vcard_[i]['BINVAL']
@@ -241,6 +232,7 @@ class ProfileWindow:
GdkPixbuf.InterpType.BILINEAR)
image.set_from_pixbuf(pixbuf)
button.show()
+ remove_avatar.show()
text_button.hide()
continue
if i == 'ADR' or i == 'TEL' or i == 'EMAIL':