Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#594: Save Image #595

Merged
merged 13 commits into from
May 25, 2024
133 changes: 112 additions & 21 deletions src/iptux/DialogBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "config.h"
#include "DialogBase.h"

#include "UiModels.h"
#include <glib/gi18n.h>
#include <glog/logging.h>
#include <sys/stat.h>
Expand Down Expand Up @@ -72,14 +73,12 @@
* 清空聊天历史记录.
*/
void DialogBase::ClearHistoryTextView() {
GtkWidget* widget;
GtkTextBuffer* buffer;
GtkTextTagTable* table;
GtkTextIter start, end;
GSList *taglist, *tlist;

widget = GTK_WIDGET(g_datalist_get_data(&widset, "history-textview-widget"));
buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
buffer = gtk_text_view_get_buffer(chat_history_widget);

Check warning on line 81 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L81

Added line #L81 was not covered by tests
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Potential null pointer dereference

Ensure that chat_history_widget is not null before calling gtk_text_view_get_buffer to avoid potential null pointer dereference.

table = gtk_text_buffer_get_tag_table(buffer);

/* 清除用于局部标记的GtkTextTag */
Expand All @@ -105,17 +104,14 @@
* 滚动聊天历史记录区.
*/
void DialogBase::ScrollHistoryTextview() {
GtkWidget* widget;
GtkTextBuffer* buffer;
GtkTextIter end;
GtkTextMark* mark;

widget = GTK_WIDGET(g_datalist_get_data(&widset, "history-textview-widget"));
buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
buffer = gtk_text_view_get_buffer(chat_history_widget);
gtk_text_buffer_get_end_iter(buffer, &end);
mark = gtk_text_buffer_create_mark(buffer, NULL, &end, FALSE);
gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(widget), mark, 0.0, TRUE, 0.0,
0.0);
gtk_text_view_scroll_to_mark(chat_history_widget, mark, 0.0, TRUE, 0.0, 0.0);
gtk_text_buffer_delete_mark(buffer, mark);
}

Expand Down Expand Up @@ -287,7 +283,6 @@
*/
GtkWidget* DialogBase::CreateHistoryArea() {
GtkWidget *frame, *sw;
GtkWidget* widget;

frame = gtk_frame_new(_("Chat History"));
gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
Expand All @@ -298,20 +293,24 @@
GTK_SHADOW_ETCHED_IN);
gtk_container_add(GTK_CONTAINER(frame), sw);

widget = gtk_text_view_new_with_buffer(grpinf->buffer);
gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(widget), FALSE);
gtk_text_view_set_editable(GTK_TEXT_VIEW(widget), FALSE);
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(widget), GTK_WRAP_WORD);
gtk_container_add(GTK_CONTAINER(sw), widget);
g_signal_connect(widget, "key-press-event",
chat_history_widget =
GTK_TEXT_VIEW(gtk_text_view_new_with_buffer(grpinf->buffer));
gtk_text_view_set_cursor_visible(chat_history_widget, FALSE);
gtk_text_view_set_editable(chat_history_widget, FALSE);
gtk_text_view_set_wrap_mode(chat_history_widget, GTK_WRAP_WORD);
gtk_container_add(GTK_CONTAINER(sw), GTK_WIDGET(chat_history_widget));
g_signal_connect(chat_history_widget, "key-press-event",
G_CALLBACK(textview_key_press_event), NULL);
g_signal_connect(widget, "event-after", G_CALLBACK(textview_event_after),
NULL);
g_signal_connect(widget, "motion-notify-event",
g_signal_connect(chat_history_widget, "event-after",
G_CALLBACK(textview_event_after), NULL);
g_signal_connect(chat_history_widget, "motion-notify-event",
G_CALLBACK(textview_motion_notify_event), NULL);
g_signal_connect(widget, "visibility-notify-event",
g_signal_connect(chat_history_widget, "visibility-notify-event",
G_CALLBACK(textview_visibility_notify_event), NULL);
g_datalist_set_data(&widset, "history-textview-widget", widget);
g_signal_connect_data(grpinf->buffer, "insert-child-anchor",
G_CALLBACK(DialogBase::OnChatHistoryInsertChildAnchor),
this, NULL,
(GConnectFlags)(G_CONNECT_AFTER | G_CONNECT_SWAPPED));

/* 滚动消息到最末位置 */
ScrollHistoryTextview();
Expand Down Expand Up @@ -795,7 +794,7 @@
return grpinf->getInputBuffer();
}

void DialogBase::OnPasteClipboard(DialogBase* self, GtkTextView* textview) {
void DialogBase::OnPasteClipboard(DialogBase*, GtkTextView* textview) {
GtkClipboard* clipboard;
GtkTextBuffer* buffer;
GtkTextIter iter;
Expand All @@ -819,4 +818,96 @@
}
}

gboolean DialogBase::OnImageButtonPress(DialogBase*,

Check warning on line 821 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L821

Added line #L821 was not covered by tests
GdkEventButton event,
GtkEventBox* event_box) {
if (event.type != GDK_BUTTON_PRESS || event.button != 3) {
return FALSE;

Check warning on line 825 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L824-L825

Added lines #L824 - L825 were not covered by tests
}

GtkWidget* image = gtk_bin_get_child(GTK_BIN(event_box));
if (!GTK_IS_IMAGE(image)) {
LOG_ERROR("image not found in event box.");
return FALSE;

Check warning on line 831 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L828-L831

Added lines #L828 - L831 were not covered by tests
}

GtkWidget* menu = gtk_menu_new();
GtkWidget* menu_item = gtk_menu_item_new_with_label(_("Save Image"));
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
g_signal_connect_swapped(menu_item, "activate",

Check warning on line 837 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L834-L837

Added lines #L834 - L837 were not covered by tests
G_CALLBACK(DialogBase::OnSaveImage), image);
gtk_widget_show_all(menu);

Check warning on line 839 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L839

Added line #L839 was not covered by tests

gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent*)&event);
return TRUE;

Check warning on line 842 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L841-L842

Added lines #L841 - L842 were not covered by tests
}

void DialogBase::OnChatHistoryInsertChildAnchor(DialogBase* self,

Check warning on line 845 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L845

Added line #L845 was not covered by tests
const GtkTextIter*,
GtkTextChildAnchor* anchor,
GtkTextBuffer*) {
const char* path =
(const char*)g_object_get_data(G_OBJECT(anchor), kObjectKeyImagePath);
if (!path) {
LOG_WARN("No image path found in anchor.");
return;

Check warning on line 853 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L850-L853

Added lines #L850 - L853 were not covered by tests
}

GtkWidget* event_box = gtk_event_box_new();
gtk_widget_set_focus_on_click(event_box, TRUE);

Check warning on line 857 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L856-L857

Added lines #L856 - L857 were not covered by tests

GtkImage* image = igtk_image_new_with_size(path, 300, 300);
if (!image) {
LOG_ERROR("Failed to create image widget.");
return;

Check warning on line 862 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L859-L862

Added lines #L859 - L862 were not covered by tests
}
g_object_set_data_full(G_OBJECT(image), kObjectKeyImagePath, g_strdup(path),

Check warning on line 864 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L864

Added line #L864 was not covered by tests
g_free);

gtk_container_add(GTK_CONTAINER(event_box), GTK_WIDGET(image));
g_signal_connect_swapped(event_box, "button-press-event",

Check warning on line 868 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L867-L868

Added lines #L867 - L868 were not covered by tests
G_CALLBACK(DialogBase::OnImageButtonPress), self);

gtk_text_view_add_child_at_anchor(self->chat_history_widget, event_box,

Check warning on line 871 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L871

Added line #L871 was not covered by tests
anchor);
gtk_widget_show_all(event_box);

Check warning on line 873 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L873

Added line #L873 was not covered by tests
}

void DialogBase::OnSaveImage(GtkImage* image) {

Check warning on line 876 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L876

Added line #L876 was not covered by tests
const char* path =
(const char*)g_object_get_data(G_OBJECT(image), kObjectKeyImagePath);
if (!path) {
LOG_ERROR("No image path found in image widget.");
return;

Check warning on line 881 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L878-L881

Added lines #L878 - L881 were not covered by tests
}

GtkWidget* dialog = gtk_file_chooser_dialog_new(
_("Save Image"), GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(image))),
GTK_FILE_CHOOSER_ACTION_SAVE, _("_Cancel"), GTK_RESPONSE_CANCEL,

Check warning on line 886 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L884-L886

Added lines #L884 - L886 were not covered by tests
_("_Save"), GTK_RESPONSE_ACCEPT, NULL);
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),

Check warning on line 888 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L888

Added line #L888 was not covered by tests
TRUE);
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "image.png");

Check warning on line 890 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L890

Added line #L890 was not covered by tests

if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
char* save_path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));

Check warning on line 893 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L892-L893

Added lines #L892 - L893 were not covered by tests

GError* error = NULL;
GFile* source = g_file_new_for_path(path);
GFile* destination = g_file_new_for_path(save_path);
gboolean success = g_file_copy(source, destination, G_FILE_COPY_OVERWRITE,

Check warning on line 898 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L895-L898

Added lines #L895 - L898 were not covered by tests
NULL, NULL, NULL, &error);
if (!success) {
LOG_ERROR("Failed to save image: %s", error->message);
g_error_free(error);

Check warning on line 902 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L900-L902

Added lines #L900 - L902 were not covered by tests
}

g_object_unref(source);
g_object_unref(destination);
g_free(save_path);

Check warning on line 907 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L905-L907

Added lines #L905 - L907 were not covered by tests
}

gtk_widget_destroy(dialog);

Check warning on line 910 in src/iptux/DialogBase.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/DialogBase.cpp#L910

Added line #L910 was not covered by tests
}

} // namespace iptux
9 changes: 9 additions & 0 deletions src/iptux/DialogBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,20 @@ class DialogBase : public SessionAbstract, public sigc::trackable {
static gboolean UpdateFileSendUI(DialogBase* dlggrp);
static void RemoveSelectedEnclosure(DialogBase* self);
static void OnPasteClipboard(DialogBase* self, GtkTextView* textview);
static gboolean OnImageButtonPress(DialogBase* self,
GdkEventButton event,
GtkEventBox* eventbox);
static void OnChatHistoryInsertChildAnchor(DialogBase* self,
const GtkTextIter* location,
GtkTextChildAnchor* anchor,
GtkTextBuffer* buffer);
static void OnSaveImage(GtkImage* self);

protected:
Application* app;
std::shared_ptr<ProgramData> progdt;

GtkTextView* chat_history_widget = 0;
GtkTreeView* fileSendTree = 0;
GtkTextView* inputTextviewWidget = 0;

Expand Down
11 changes: 11 additions & 0 deletions src/iptux/DialogPeerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,16 @@ TEST(DialogPeer, Constructor) {
do_action(dlgpr, "paste");
g_object_unref(pixbuf);

grpinf->buffer = gtk_text_buffer_new(NULL);
MsgPara msg(pal);
msg.dtlist.push_back(ChipData("helloworld"));

grpinf->addMsgPara(msg);

msg = MsgPara(pal);
msg.dtlist.push_back(
ChipData(MessageContentType::PICTURE, testDataPath("iptux.png")));
grpinf->addMsgPara(msg);

DestroyApplication(app);
}
13 changes: 13 additions & 0 deletions src/iptux/UiHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -363,4 +363,17 @@
return s.substr(pos, pos2 - pos);
}

GtkImage* igtk_image_new_with_size(const char* filename,
int width,
int height) {
GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
if (!pixbuf) {
LOG_ERROR("Error loading image.");
return NULL;

Check warning on line 372 in src/iptux/UiHelper.cpp

View check run for this annotation

Codecov / codecov/patch

src/iptux/UiHelper.cpp#L371-L372

Added lines #L371 - L372 were not covered by tests
}

pixbuf_shrink_scale_1(&pixbuf, width, height);
return GTK_IMAGE(gtk_image_new_from_pixbuf(pixbuf));
}

} // namespace iptux
10 changes: 10 additions & 0 deletions src/iptux/UiHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ void pixbuf_shrink_scale_1(GdkPixbuf** pixbuf, int width, int height);
void widget_enable_dnd_uri(GtkWidget* widget);
GSList* selection_data_get_path(GtkSelectionData* data);

/**
* @brief create GtkImage with width and height
*
* @param filename image file name
* @param width max width, -1 for no limit
* @param height max height, -1 for no limit
* @return GtkImage* null if failed
*/
GtkImage* igtk_image_new_with_size(const char* filename, int width, int height);

/**
* @brief only used for test, after call this, pop_info, pop_warning,
* and iptux_open_url will only print log
Expand Down
20 changes: 20 additions & 0 deletions src/iptux/UiHelperTest.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "gtest/gtest.h"

#include "iptux-utils/TestHelper.h"
#include "iptux/UiHelper.h"
#include <cstdlib>
#include <ctime>
Expand Down Expand Up @@ -43,3 +44,22 @@ TEST(UiHelper, StrFirstNonEmptyLine) {
ASSERT_EQ(StrFirstNonEmptyLine("\n b"), "b");
ASSERT_EQ(StrFirstNonEmptyLine(" \n b\n"), "b");
}

TEST(UiHelper, igtk_image_new_with_size) {
auto image =
igtk_image_new_with_size(testDataPath("iptux.png").c_str(), 100, 100);
ASSERT_NE(image, nullptr);
auto pixbuf = gtk_image_get_pixbuf(image);
ASSERT_EQ(gdk_pixbuf_get_width(pixbuf), 48);
ASSERT_EQ(gdk_pixbuf_get_height(pixbuf), 48);
g_object_unref(pixbuf);
g_object_unref(image);

image = igtk_image_new_with_size(testDataPath("iptux.png").c_str(), 20, 30);
ASSERT_NE(image, nullptr);
pixbuf = gtk_image_get_pixbuf(image);
ASSERT_EQ(gdk_pixbuf_get_width(pixbuf), 20);
ASSERT_EQ(gdk_pixbuf_get_height(pixbuf), 20);
g_object_unref(pixbuf);
g_object_unref(image);
}
31 changes: 12 additions & 19 deletions src/iptux/UiModels.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ using namespace std;

namespace iptux {

const char* const kObjectKeyImagePath = "image-path";

/**
* 文件传输树(trans-tree)底层数据结构.
* 14,0 status,1 task,2 peer,3 ip,4 filename,5 filelength,6 finishlength,7
Expand Down Expand Up @@ -665,30 +667,21 @@ static void InsertHeaderToBuffer(GtkTextBuffer* buffer,
}
}

#define OCCUPY_OBJECT 0x01

/**
* 插入图片到TextBuffer(非UI线程安全).
* 插入图片到TextBuffer.
* @param buffer text-buffer
* @param path 图片路径
*/
static void InsertPixbufToBuffer(GtkTextBuffer* buffer, const gchar* path) {
GtkTextIter start, end;
GdkPixbuf* pixbuf;

if ((pixbuf = gdk_pixbuf_new_from_file(path, NULL))) {
gtk_text_buffer_get_start_iter(buffer, &start);
if (gtk_text_iter_get_char(&start) == OCCUPY_OBJECT ||
gtk_text_iter_forward_find_char(
&start, GtkTextCharPredicate(giter_compare_foreach),
GUINT_TO_POINTER(OCCUPY_OBJECT), NULL)) {
end = start;
gtk_text_iter_forward_char(&end);
gtk_text_buffer_delete(buffer, &start, &end);
}
gtk_text_buffer_insert_pixbuf(buffer, &start, pixbuf);
g_object_unref(pixbuf);
}
GtkTextIter iter;

gtk_text_buffer_get_end_iter(buffer, &iter);
GtkTextChildAnchor* anchor = gtk_text_child_anchor_new();
g_object_set_data_full(G_OBJECT(anchor), kObjectKeyImagePath, g_strdup(path),
GDestroyNotify(g_free));
gtk_text_buffer_insert_child_anchor(buffer, &iter, anchor);
gtk_text_buffer_get_end_iter(buffer, &iter);
gtk_text_buffer_insert(buffer, &iter, "\n", -1);
}

void GroupInfo::addMsgPara(const MsgPara& para) {
Expand Down
2 changes: 2 additions & 0 deletions src/iptux/UiModels.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

namespace iptux {

extern const char* const kObjectKeyImagePath;

typedef void (*GActionCallback)(GSimpleAction* action,
GVariant* parameter,
gpointer user_data);
Expand Down
Loading
Loading