Skip to content

Commit

Permalink
#594: Support save Image from chat history (#595)
Browse files Browse the repository at this point in the history
  • Loading branch information
lidaobing authored May 25, 2024
1 parent d6ce292 commit 906cb83
Show file tree
Hide file tree
Showing 12 changed files with 282 additions and 55 deletions.
21 changes: 21 additions & 0 deletions src/iptux-utils/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,27 @@ char* getformattime(gboolean date, const char* format, ...) {
return ptr;
}

char* getformattime2(time_t tt, gboolean date, const char* format, ...) {
char buf[MAX_BUFLEN], *msg, *ptr;
va_list ap;

va_start(ap, format);
msg = g_strdup_vprintf(format, ap);
va_end(ap);

struct tm tm;
localtime_r(&tt, &tm);
if (date)
strftime(buf, MAX_BUFLEN, "%c", &tm);
else
strftime(buf, MAX_BUFLEN, "%X", &tm);

ptr = g_strdup_printf("(%s) %s:", buf, msg);
g_free(msg);

return ptr;
}

/**
* 对GtkTextBuffer的迭代器(GtkTextIter)所指的字符进行比较.
* @param src 源字符
Expand Down
4 changes: 3 additions & 1 deletion src/iptux-utils/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ char* convert_encode(const char* string,
const char* fromcode);
std::string assert_filename_inexist(const char* path);
std::string dupPath(const std::string& fname, int idx);
char* getformattime(gboolean date, const char* format, ...);
char* getformattime(gboolean date, const char* format, ...) G_GNUC_PRINTF(2, 3);
char* getformattime2(time_t now, gboolean date, const char* format, ...)
G_GNUC_PRINTF(3, 4);

gboolean giter_compare_foreach(gunichar src, gunichar dst);

Expand Down
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::ClearSublayerGeneral() {
* 清空聊天历史记录.
*/
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);
table = gtk_text_buffer_get_tag_table(buffer);

/* 清除用于局部标记的GtkTextTag */
Expand All @@ -105,17 +104,14 @@ void DialogBase::ClearHistoryTextView() {
* 滚动聊天历史记录区.
*/
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::CreateInputArea() {
*/
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 @@ GtkWidget* DialogBase::CreateHistoryArea() {
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 @@ GtkTextBuffer* DialogBase::getInputBuffer() {
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 @@ void DialogBase::OnPasteClipboard(DialogBase* self, GtkTextView* textview) {
}
}

gboolean DialogBase::OnImageButtonPress(DialogBase*,
GdkEventButton event,
GtkEventBox* event_box) {
if (event.type != GDK_BUTTON_PRESS || event.button != 3) {
return FALSE;
}

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;
}

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",
G_CALLBACK(DialogBase::OnSaveImage), image);
gtk_widget_show_all(menu);

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

void DialogBase::OnChatHistoryInsertChildAnchor(DialogBase* self,
const GtkTextIter*,
GtkTextChildAnchor* anchor,
GtkTextBuffer*) {
const char* path =
(const char*)g_object_get_data(G_OBJECT(anchor), kObjectKeyImagePath);
if (!path) {
LOG_ERROR("No image path found in anchor.");
return;
}

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

GtkImage* image = igtk_image_new_with_size(path, 300, 300);
if (!image) {
LOG_ERROR("Failed to create image widget.");
return;
}
g_object_set_data_full(G_OBJECT(image), kObjectKeyImagePath, g_strdup(path),
g_free);

gtk_container_add(GTK_CONTAINER(event_box), GTK_WIDGET(image));
g_signal_connect_swapped(event_box, "button-press-event",
G_CALLBACK(DialogBase::OnImageButtonPress), self);

gtk_text_view_add_child_at_anchor(self->chat_history_widget, event_box,
anchor);
gtk_widget_show_all(event_box);
}

void DialogBase::OnSaveImage(GtkImage* image) {
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;
}

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,
_("_Save"), GTK_RESPONSE_ACCEPT, NULL);
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),
TRUE);
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "image.png");

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

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,
NULL, NULL, NULL, &error);
if (!success) {
LOG_ERROR("Failed to save image: %s", error->message);
g_error_free(error);
}

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

gtk_widget_destroy(dialog);
}

} // 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
14 changes: 10 additions & 4 deletions src/iptux/DialogPeer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -427,10 +427,16 @@ bool DialogPeer::SendTextMsg() {
/* 保存图片 */
chipmsg = g_strdup_printf("%s" IPTUX_PATH "/%" PRIx32,
g_get_user_config_dir(), count++);
gdk_pixbuf_save(pixbuf, chipmsg, "bmp", NULL, NULL);
/* 新建一个碎片数据(图片),并加入数据链表 */
ChipData chip(MESSAGE_CONTENT_TYPE_PICTURE, chipmsg);
dtlist.push_back(std::move(chip));
GError* error = nullptr;
gdk_pixbuf_save(pixbuf, chipmsg, "png", &error, NULL);
if (error) {
LOG_ERROR("failed to save image: %s", error->message);
g_error_free(error);
} else {
/* 新建一个碎片数据(图片),并加入数据链表 */
ChipData chip(MESSAGE_CONTENT_TYPE_PICTURE, chipmsg);
dtlist.push_back(std::move(chip));
}
}
} while (gtk_text_iter_forward_find_char(
&iter, GtkTextCharPredicate(giter_compare_foreach),
Expand Down
11 changes: 11 additions & 0 deletions src/iptux/DialogPeerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ TEST(DialogPeer, Constructor) {
app->getCoreThread()->AttachPalToList(pal);

GroupInfo* grpinf = app->getCoreThread()->GetPalRegularItem(pal.get());
grpinf->buffer = gtk_text_buffer_new(NULL);
DialogPeer* dlgpr = new DialogPeer(app, grpinf);

auto clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
Expand All @@ -38,5 +39,15 @@ TEST(DialogPeer, Constructor) {
do_action(dlgpr, "paste");
g_object_unref(pixbuf);

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 @@ string StrFirstNonEmptyLine(const string& s) {
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;
}

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);
}
Loading

0 comments on commit 906cb83

Please sign in to comment.